;;; hash.ms
;;; Copyright 1984-2017 Cisco Systems, Inc.
;;; 
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
;;; You may obtain a copy of the License at
;;; 
;;; http://www.apache.org/licenses/LICENSE-2.0
;;; 
;;; Unless required by applicable law or agreed to in writing, software
;;; distributed under the License is distributed on an "AS IS" BASIS,
;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;; See the License for the specific language governing permissions and
;;; limitations under the License.

(mat old-hash-table
  (error? (get-hash-table '((a . b)) 'a #f))
  (error? (put-hash-table! (list (cons 'a 'b)) 'a 'b))
  (error? (remove-hash-table! (list (cons 'a 'b)) 'a))
  (error? (hash-table-map '((a . b)) cons))
  (error? (hash-table-for-each '((a . b)) cons))
  (begin
    (define $h-ht (make-hash-table))
    (hash-table? $h-ht))
  (not (hash-table? 3))
  (not (hash-table? '$h-ht))
  (null? (hash-table-map $h-ht list))
  (eq? (let ([n 0])
         (hash-table-for-each $h-ht (lambda (x y) (set! n (+ n 1))))
         n)
       0)
  (equal?
    (begin
      (put-hash-table! $h-ht 'ham 'spam)
      (hash-table-map $h-ht list))
    '((ham spam)))
  (error? ; wrong number of args
    (hash-table-map $h-ht (lambda (x) x)))
  (error? ; wrong number of args
    (hash-table-for-each $h-ht (lambda (x) x)))
  ((lambda (x y) (or (equal? x y) (equal? x (reverse y))))
    (begin
      (put-hash-table! $h-ht 'cram 'sham)
      (hash-table-map $h-ht list))
    '((ham spam) (cram sham)))
  ((lambda (x y) (or (equal? x y) (equal? x (reverse y))))
    (begin
      (put-hash-table! $h-ht 'ham 'jam)
      (hash-table-map $h-ht list))
    '((ham jam) (cram sham)))
  (eq? (get-hash-table $h-ht 'ham #f) 'jam)
  (eq? (get-hash-table $h-ht 'cram #f) 'sham)
  (eq? (get-hash-table $h-ht 'sham #f) #f)
  (equal? (get-hash-table $h-ht 'jam "rats") "rats")
  (eq? (let ([n 0])
         (hash-table-for-each $h-ht (lambda (x y) (set! n (+ n 1))))
         n)
       2)
  ((lambda (x y) (or (equal? x y) (equal? x (reverse y))))
   (let ([keys '()] [vals '()])
     (hash-table-for-each $h-ht
       (lambda (k v)
         (set! keys (cons k keys))
         (set! vals (cons v vals))))
     (map cons vals keys))
   '((jam . ham) (sham . cram)))
  (eq? (collect (collect-maximum-generation)) (void))
  ((lambda (x y) (or (equal? x y) (equal? x (reverse y))))
   (let ([keys '()] [vals '()])
     (hash-table-for-each $h-ht
       (lambda (k v)
         (set! keys (cons k keys))
         (set! vals (cons v vals))))
     (map cons vals keys))
   '((jam . ham) (sham . cram)))
  (eq? (begin
         (remove-hash-table! $h-ht 'ham)
         (get-hash-table $h-ht 'ham 'gone!))
       'gone!)
  (equal?
    (hash-table-map $h-ht list)
    '((cram sham)))
  (eq? (collect (collect-maximum-generation)) (void))
  (equal?
    (hash-table-map $h-ht list)
    '((cram sham)))
  (eq? (begin
         (remove-hash-table! $h-ht 'ham)
         (get-hash-table $h-ht 'ham 'gone!))
       'gone!)
  (equal?
    (hash-table-map $h-ht list)
    '((cram sham)))
  (eq? (begin
         (remove-hash-table! $h-ht 'sham)
         (get-hash-table $h-ht 'ham 'never-there!))
       'never-there!)
  (equal?
    (hash-table-map $h-ht list)
    '((cram sham)))
  (eq? (begin
         (remove-hash-table! $h-ht 'cram)
         (get-hash-table $h-ht 'cram 'gone-too!))
       'gone-too!)
  (null? (hash-table-map $h-ht list))

 ; fasling out eq hash tables
  (equal?
    (let ([x (cons 'y '!)])
      (define ht (make-hash-table))
      (put-hash-table! ht x 'because)
      (put-hash-table! ht 'foo "foo")
      (let ([p (open-file-output-port "testfile.ss" (file-options replace))])
        (fasl-write (list x ht) p)
        (close-port p))
      (let-values ([(x2 ht2)
                    (apply values
                      (call-with-port
                        (open-file-input-port "testfile.ss")
                        fasl-read))])
        (list
          (get-hash-table ht2 x2 #f)
          (get-hash-table ht2 'foo #f))))
    '(because "foo"))

 ; weak hash table tests
  (begin
    (define $h-ht (make-hash-table #t))
    (hash-table? $h-ht))
  (null?
    (begin
      (put-hash-table! $h-ht (string #\a) 'yea!)
      (collect (collect-maximum-generation))
      (hash-table-map $h-ht cons)))
  (eq? (let ([n 0])
         (hash-table-for-each $h-ht (lambda (x y) (set! n (+ n 1))))
         n)
       0)
  (let ([s (string #\a)])
    (put-hash-table! $h-ht s 666)
    (equal? (get-hash-table $h-ht s #f) 666))
  (null?
    (begin
      (collect (collect-maximum-generation))
      (hash-table-map $h-ht cons)))

 ; make sure that nonweak hash tables are nonweak (explicit #f arg)
  (begin
    (define $h-ht (make-hash-table #f))
    (hash-table? $h-ht))
  (equal?
    (begin
      (put-hash-table! $h-ht (string #\a) "bc")
      (collect (collect-maximum-generation))
      (hash-table-map $h-ht string-append))
    '("abc"))

 ; make sure that nonweak hash tables are nonweak (implicit #f arg)
  (begin
    (define $h-ht (make-hash-table))
    (hash-table? $h-ht))
  (equal?
    (begin
      (put-hash-table! $h-ht (string #\a) "bc")
      (collect (collect-maximum-generation))
      (hash-table-map $h-ht string-append))
    '("abc"))

 ; stress tests
  (let () ; nonweak
    (define pick
      (lambda (ls)
        (list-ref ls (random (length ls)))))
    (define ht (make-hash-table))
    (let* ([ls (remq '|| (oblist))] [n 50000])
      (let f ([i 0] [keep '()] [drop '()])
        (if (= i n)
            (and (= (length (hash-table-map ht (lambda (x y) x)))
                    (- n (length drop)))
                 (andmap (lambda (k)
                           (string=?
                             (symbol->string (get-hash-table ht k #f))
                             (cond
                               [(string? k) k]
                               [(pair? k) (car k)]
                               [(vector? k) (vector-ref k 0)])))
                         keep)
                 (andmap (lambda (k) (eq? (get-hash-table ht k 'no) 'no))
                         drop))
            (let* ([x (pick ls)] [s (string-copy (symbol->string x))])
              (let ([k (case (pick '(string pair vector))
                         [(string) s]
                         [(pair) (list s)]
                         [(vector) (vector s)])])
                (put-hash-table! ht k x)
                (let ([keep (if (= (modulo i 5) 0) (cons k keep) keep)])
                  (if (= (modulo i 17) 5)
                      (let ([k (pick keep)])
                        (remove-hash-table! ht k)
                        (let ([drop (cons k drop)])
                          (when (= (random 5) 3)
                            (remove-hash-table! ht (pick drop)))
                          (f (+ i 1) (remq k keep) drop)))
                      (f (+ i 1) keep drop)))))))))

  (let () ; weak
    (define pick
      (lambda (ls)
        (list-ref ls (random (length ls)))))
    (define ht (make-hash-table #t))
    (let* ([ls (remq '|| (oblist))] [n 50000])
      (let f ([i 0] [keep '()] [drop '()])
        (if (= i n)
            (and (<= (length (hash-table-map ht (lambda (x y) x)))
                     (- n (length drop)))
                 (begin
                   (collect (collect-maximum-generation))
                   (= (length (hash-table-map ht (lambda (x y) x)))
                      (length keep)))
                 (andmap (lambda (k)
                           (string=?
                             (symbol->string (get-hash-table ht k #f))
                             (cond
                               [(string? k) k]
                               [(pair? k) (car k)]
                               [(vector? k) (vector-ref k 0)])))
                         keep)
                 (andmap (lambda (k) (eq? (get-hash-table ht k 'no) 'no))
                         drop))
            (let* ([x (pick ls)] [s (string-copy (symbol->string x))])
              (let ([k (case (pick '(string pair vector))
                         [(string) s]
                         [(pair) (list s)]
                         [(vector) (vector s)])])
                (put-hash-table! ht k x)
                (let ([keep (if (= (modulo i 5) 0) (cons k keep) keep)])
                  (if (= (modulo i 17) 5)
                      (let ([k (pick keep)])
                        (remove-hash-table! ht k)
                        (let ([drop (cons k drop)])
                          (when (= (random 5) 3)
                            (remove-hash-table! ht (pick drop)))
                          (f (+ i 1) (remq k keep) drop)))
                      (f (+ i 1) keep drop)))))))))
)

(mat tlc
  (critical-section
    (let ()
      (define ht (make-eq-hashtable))
      (define keyval '(a . b))
      (define next 0)
      (define tlc (#%$make-tlc ht keyval next))
      (define tlc2 (#%$make-tlc ht keyval next))
      (and
        (#%$tlc? tlc)
        (not (#%$tlc? keyval))
        (eq? (#%$tlc-ht tlc) ht)
        (eq? (#%$tlc-keyval tlc) keyval)
        (eqv? (#%$tlc-next tlc) next)
        (begin
          (#%$set-tlc-next! tlc tlc2)
          (eq? (#%$tlc-next tlc) tlc2)))))
)

(define $vector-andmap
  (lambda (p . v*)
    (apply andmap p (map vector->list v*))))

(define $vector-append
  (lambda v*
    (list->vector (apply append (map vector->list v*)))))

(define $vector-member?
  (lambda (x v)
    (let ([n (vector-length v)])
      (let f ([i 0])
        (and (not (fx= i n))
          (or (equal? (vector-ref v i) x)
              (f (fx+ i 1))))))))

(define same-elements?
  (lambda (v1 v2)
    (let ([n (vector-length v1)])
      (define (each-in? v1 v2)
        (let f ([i 0])
          (or (fx= i n)
              (and ($vector-member? (vector-ref v1 i) v2)
                   (f (fx+ i 1))))))
      (and (fx= (vector-length v2) n)
           (each-in? v1 v2)
           (each-in? v2 v1)))))

(define equal-entries?
  (lambda (ht keys vals)
    (define-syntax same-entries?
      (syntax-rules ()
        [(_ e1 keys2 vals2)
         (let-values ([(keys1 vals1) e1])
           (and
            (same-elements? keys1 keys2)
            (same-elements? vals1 vals2)))]))

    (and
     (same-elements? (hashtable-keys ht) keys)
     (same-elements? (hashtable-values ht) vals)
     (same-entries? (hashtable-entries ht) keys vals)
     (same-elements? (hashtable-cells ht) (vector-map cons keys vals))

     (same-elements? (r6rs:hashtable-keys ht) keys)
     (same-entries? (r6rs:hashtable-entries ht) keys vals)

     ;; Check requested sizes > hash table size
     (andmap (lambda (size)
               (and
                (same-elements? (hashtable-keys ht size) keys)
                (same-elements? (hashtable-values ht size) vals)
                (same-entries? (hashtable-entries ht size) keys vals)
                (same-elements? (hashtable-cells ht size) (vector-map cons keys vals))))
             (list (add1 (hashtable-size ht))
                   (expt 2 1000)))

     ;; Make sure request of 0 always works:
     (same-elements? (hashtable-keys ht 0) '#())
     (same-elements? (hashtable-values ht 0) '#())
     (same-entries? (hashtable-entries ht 0) '#() '#())
     (same-elements? (hashtable-cells ht 0) '#())

     (or
      (< (hashtable-size ht) 2)
      ;; Check request of size 2:
      (let ([twos (lambda (v)
                     (let i-loop ([i 0])
                       (cond
                        [(= i (vector-length v))
                         '()]
                        [else
                         (let j-loop ([j (add1 i)])
                           (cond
                            [(= j (vector-length v))
                             (i-loop (add1 i))]
                            [else
                             (cons (vector (vector-ref v i) (vector-ref v j))
                                   (j-loop (add1 j)))]))])))])
        (let ([keyss (twos keys)]
              [valss (twos vals)])
          (and
           (let ([got-keys (hashtable-keys ht 2)])
             (ormap (lambda (keys)
                      (same-elements? got-keys keys))
                    keyss))
           (let ([got-vals (hashtable-values ht 2)])
             (ormap (lambda (vals)
                      (same-elements? got-vals vals))
                    valss))
           (let-values ([(got-keys got-vals) (hashtable-entries ht 2)])
             (ormap (lambda (keys vals)
                      (and (same-elements? got-keys keys)
                           (same-elements? got-vals vals)))
                    keyss valss))
           (let ([got-cells (hashtable-cells ht 2)])
             (ormap (lambda (keys vals)
                      (same-elements? got-cells (vector-map cons keys vals)))
                    keyss valss)))))))))

(mat hashtable-arguments
 ; make-eq-hashtable
  (error? ; wrong argument count
    (make-eq-hashtable 3 #t))
  (error? ; invalid size
    (make-eq-hashtable -1))
  (error? ; invalid size
    (make-eq-hashtable #t))
  (error? ; invalid size
    (make-eq-hashtable #f))
 ; make-hashtable
  (error? ; wrong argument count
    (make-hashtable))
  (error? ; wrong argument count
    (make-hashtable equal-hash))
  (error? ; wrong argument count
    (make-hashtable equal-hash equal? 45 53))
  (error? ; not a procedure
    (make-hashtable 'a equal? 45))
  (error? ; not a procedure
    (make-hashtable equal-hash 'a 45))
  (error? ; invalid size
    (make-hashtable equal-hash equal? 'a))
  (error? ; invalid size
    (make-hashtable equal-hash equal? -45))
  (error? ; invalid size
    (make-hashtable equal-hash equal? 45.0))
 ; make-eqv-hashtable
  (error? ; wrong argument count
    (make-eqv-hashtable 3 #t))
  (error? ; invalid size
    (make-eqv-hashtable -1))
  (error? ; invalid size
    (make-eqv-hashtable #t))
  (error? ; invalid size
    (make-eqv-hashtable #f))
  (begin
    (define $ht (make-eq-hashtable))
    (define $imht (hashtable-copy $ht))
    (define $ht2 (make-eq-hashtable 50))
    (and (hashtable? $ht)
         (eq-hashtable? $ht)
         (hashtable-mutable? $ht)
         (not (hashtable-weak? $ht))
         (not (eq-hashtable-weak? $ht))
         (not (hashtable-ephemeron? $ht))
         (not (eq-hashtable-ephemeron? $ht))
         (hashtable? $imht)
         (eq-hashtable? $imht)
         (not (hashtable-mutable? $imht))
         (not (hashtable-weak? $imht))
         (not (eq-hashtable-weak? $imht))
         (not (hashtable-ephemeron? $imht))
         (not (eq-hashtable-ephemeron? $imht))
         (hashtable? $ht2)
         (eq-hashtable? $ht2)
         (hashtable-mutable? $ht2)
         (not (hashtable-weak? $ht2))
         (not (eq-hashtable-weak? $ht2))
         (not (hashtable-ephemeron? $ht2))
         (not (eq-hashtable-ephemeron? $ht2))))
  (not (hashtable? 3))
  (not (hashtable? (make-vector 3)))
  (not (eq-hashtable? 3))
  (not (eq-hashtable? (make-vector 3)))
 ; hashtable?
  (error? ; wrong argument count
    (hashtable?))
  (error? ; wrong argument count
    (hashtable? $ht 3))
  (error? ; wrong argument count
    (eq-hashtable?))
  (error? ; wrong argument count
    (eq-hashtable? $ht 3))
 ; hashtable-mutable?
  (error? ; not a hashtable
    (hashtable-mutable? (make-vector 3)))
  (error? ; wrong argument count
    (hashtable-mutable?))
  (error? ; wrong argument count
    (hashtable-mutable? $ht 3))
 ; hashtable-size
  (error? ; wrong argument count
    (hashtable-size))
  (error? ; wrong argument count
    (hashtable-size $ht 3))
  (error? ; not a hashtable
    (hashtable-size 'hello))
 ; hashtable-ref
  (error? ; wrong argument count
    (hashtable-ref))
  (error? ; wrong argument count
    (hashtable-ref $ht))
  (error? ; wrong argument count
    (hashtable-ref $ht 'a))
  (error? ; wrong argument count
    (hashtable-ref $ht 'a 'b 'c))
  (error? ; not a hashtable
    (hashtable-ref '(hash . table) 'a 'b))
 ; hashtable-contains?
  (error? ; wrong argument count
    (hashtable-contains?))
  (error? ; wrong argument count
    (hashtable-contains? $ht))
  (error? ; wrong argument count
    (hashtable-contains? $ht 'a 'b))
  (error? ; not a hashtable
    (hashtable-contains? '(hash . table) 'a))
 ; hashtable-set!
  (error? ; wrong argument count
    (hashtable-set!))
  (error? ; wrong argument count
    (hashtable-set! $ht))
  (error? ; wrong argument count
    (hashtable-set! $ht 'a))
  (error? ; wrong argument count
    (hashtable-set! $ht 'a 'b 'c))
  (error? ; not a hashtable
    (hashtable-set! '(hash . table) 'a 'b))
  (error? ; hashtable not mutable
    (hashtable-set! $imht 'a 'b))
 ; hashtable-update!
  (error? ; wrong argument count
    (hashtable-update!))
  (error? ; wrong argument count
    (hashtable-update! $ht))
  (error? ; wrong argument count
    (hashtable-update! $ht 'a values))
  (error? ; wrong argument count
    (hashtable-update! $ht 'a values 'c 'd))
  (error? ; not a hashtable
    (hashtable-update! '(hash . table) 'a values 'b))
  (error? ; hashtable not mutable
    (hashtable-update! $imht 'a values 'b))
  (error? ; not a procedure
    (hashtable-update! $ht 'a "not a procedure" 'b))
 ; hashtable-cell
  (error? ; wrong argument count
    (hashtable-cell))
  (error? ; wrong argument count
    (hashtable-cell $ht))
  (error? ; wrong argument count
    (hashtable-cell $ht 'a))
  (error? ; wrong argument count
    (hashtable-cell $ht 'a 'b 'c))
  (error? ; not a hashtable
    (hashtable-cell '(hash . table) 'a 'b))
 ; hashtable-delete!
  (error? ; wrong argument count
    (hashtable-delete!))
  (error? ; wrong argument count
    (hashtable-delete! $ht))
  (error? ; wrong argument count
    (hashtable-delete! $ht 'a 'b))
  (error? ; not a hashtable
    (hashtable-delete! '(hash . table) 'a))
  (error? ; hashtable not mutable
    (hashtable-delete! $imht 'a))
 ; hashtable-copy
  (error? ; wrong argument count
    (hashtable-copy))
  (error? ; wrong argument count
    (hashtable-copy $ht #t 17))
  (error? ; not a hashtable
    (hashtable-copy '(hash . table) #t))
 ; hashtable-clear!
  (error? ; wrong argument count
    (hashtable-clear!))
  (error? ; wrong argument count
    (hashtable-clear! $ht 17 'foo))
  (error? ; not a hashtable
    (hashtable-clear! '(hash . table)))
  (error? ; not a hashtable
    (hashtable-clear! '(hash . table) 17))
  (error? ; hashtable not mutable
    (hashtable-clear! $imht))
  (error? ; hashtable not mutable
    (hashtable-clear! $imht 32))
  (error? ; invalid size
    (hashtable-clear! $ht #t))
 ; hashtable-keys
  (error? ; wrong argument count
    (hashtable-keys))
  (error? ; wrong argument count
    (hashtable-keys $ht 72 43))
  (error? ; not a hashtable
    (hashtable-keys '(hash . table)))
  (error? ; bad size
    (hashtable-keys $ht -79))
  (error? ; bad size
    (hashtable-keys $ht 'not-an-unsigned-integer))
  (error? ; wrong argument count
    (r6rs:hashtable-keys))
  (error? ; wrong argument count
    (r6rs:hashtable-keys $ht 72))
  (error? ; not a hashtable
    (r6rs:hashtable-keys '(hash . table)))
 ; hashtable-values
  (error? ; wrong argument count
    (hashtable-values))
  (error? ; wrong argument count
    (hashtable-values $ht 72 43))
  (error? ; not a hashtable
    (hashtable-values '(hash . table)))
  (error? ; bad size
    (hashtable-values $ht -79))
  (error? ; bad size
    (hashtable-values $ht 'not-an-unsigned-integer))
 ; hashtable-entries
  (error? ; wrong argument count
    (hashtable-entries))
  (error? ; wrong argument count
    (hashtable-entries $ht 72 43))
  (error? ; not a hashtable
    (hashtable-entries '(hash . table)))
  (error? ; bad size
    (hashtable-entries $ht -79))
  (error? ; bad size
    (hashtable-entries $ht 'not-an-unsigned-integer))
  (error? ; wrong argument count
    (r6rs:hashtable-entries))
  (error? ; wrong argument count
    (r6rs:hashtable-entries $ht 72))
  (error? ; not a hashtable
    (r6rs:hashtable-entries '(hash . table)))
 ; hashtable-cells
  (error? ; wrong argument count
    (hashtable-cells))
  (error? ; wrong argument count
    (hashtable-cells $ht 72 43))
  (error? ; not a hashtable
    (hashtable-cells '(hash . table)))
  (error? ; bad size
    (hashtable-cells $ht -79))
  (error? ; bad size
    (hashtable-cells $ht 'not-an-unsigned-integer))
 ; hashtable-hash-function
  (error? ; wrong argument count
    (hashtable-hash-function))
  (error? ; wrong argument count
    (hashtable-hash-function $ht $ht))
  (error? ; not a hsshtable
    (hashtable-hash-function '(hash . table)))
 ; hashtable-equivalence-function
  (error? ; wrong argument count
    (hashtable-equivalence-function))
  (error? ; wrong argument count
    (hashtable-equivalence-function $ht $ht))
  (error? ; not a hsshtable
    (hashtable-equivalence-function '(hash . table)))
 ; hashtable-weak?
  (error? ; wrong argument count
    (hashtable-weak?))
  (error? ; wrong argument count
    (hashtable-weak? $ht 3))
  (error? ; not a hashtable
    (hashtable-weak? '(hash . table)))
 ; hashtable-ephemeron?
  (error? ; wrong argument count
    (hashtable-ephemeron?))
  (error? ; wrong argument count
    (hashtable-ephemeron? $ht 3))
  (error? ; not a hashtable
    (hashtable-ephemeron? '(hash . table)))
)

(mat hash-return-value
  ; hashtable-ref
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) "oops") equal?)])
      (hashtable-ref ht 'any #f)))
  #;(error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) -7) equal?)])
      (hashtable-ref ht 'any #f)))
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) 3.5) equal?)])
      (hashtable-ref ht 'any #f)))
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) 1+2i) equal?)])
      (hashtable-ref ht 'any #f)))
  ; hashtable-contains?
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) "oops") equal?)])
      (hashtable-contains? ht 'any)))
  #;(error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) -7) equal?)])
      (hashtable-contains? ht 'any)))
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) 3.5) equal?)])
      (hashtable-contains? ht 'any)))
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) 1+2i) equal?)])
      (hashtable-contains? ht 'any)))
  ; hashtable-set!
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) "oops") equal?)])
      (hashtable-set! ht 'any 'spam)))
  #;(error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) -7) equal?)])
      (hashtable-set! ht 'any 'spam)))
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) 3.5) equal?)])
      (hashtable-set! ht 'any 'spam)))
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) 1+2i) equal?)])
      (hashtable-set! ht 'any 'spam)))
  ; hashtable-update!
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) "oops") equal?)])
      (hashtable-update! ht 'any values 'spam)))
  #;(error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) -7) equal?)])
      (hashtable-update! ht 'any values 'spam)))
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) 3.5) equal?)])
      (hashtable-update! ht 'any values 'spam)))
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) 1+2i) equal?)])
      (hashtable-update! ht 'any values 'spam)))
  ; hashtable-cell
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) "oops") equal?)])
      (hashtable-cell ht 'any 0)))
  #;(error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) -7) equal?)])
      (hashtable-cell ht 'any 0)))
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) 3.5) equal?)])
      (hashtable-cell ht 'any 0)))
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) 1+2i) equal?)])
      (hashtable-cell ht 'any 0)))
  ; hashtable-delete!
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) "oops") equal?)])
      (hashtable-delete! ht 'any)))
  #;(error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) -7) equal?)])
      (hashtable-delete! ht 'any)))
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) 3.5) equal?)])
      (hashtable-delete! ht 'any)))
  (error? ; invalid hash-function return value
    (let ([ht (make-hashtable (lambda (x) 1+2i) equal?)])
      (hashtable-delete! ht 'any)))
)

(mat eq-hashtable-arguments
 ; make-weak-eq-hashtable
  (error? ; wrong argument count
    (make-weak-eq-hashtable 3 #t))
  (error? ; invalid size
    (make-weak-eq-hashtable -1))
  (error? ; invalid size
    (make-weak-eq-hashtable #t))
  (error? ; invalid size
    (make-weak-eq-hashtable #f))
 ; make-weak-eq-hashtable
  (error? ; wrong argument count
    (make-ephemeron-eq-hashtable 3 #t))
  (error? ; invalid size
    (make-ephemeron-eq-hashtable -1))
  (error? ; invalid size
    (make-ephemeron-eq-hashtable #t))
  (error? ; invalid size
    (make-ephemeron-eq-hashtable #f))
  (begin
    (define $wht (make-weak-eq-hashtable 50))
    (define $eht (make-ephemeron-eq-hashtable 50))
    (define $imht (hashtable-copy $wht))
    (define $imeht (hashtable-copy $eht))
    (define $wht2 (make-weak-eq-hashtable))
    (define $eht2 (make-ephemeron-eq-hashtable))
    (and (hashtable? $wht)
         (hashtable? $eht)
         (eq-hashtable? $wht)
         (eq-hashtable? $eht)
         (hashtable-weak? $wht)
         (not (hashtable-ephemeron? $wht))
         (hashtable-ephemeron? $eht)
         (not (hashtable-weak? $eht))
         (eq-hashtable-weak? $wht)
         (not (eq-hashtable-ephemeron? $wht))
         (eq-hashtable-ephemeron? $eht)
         (not (eq-hashtable-weak? $eht))
         (hashtable-mutable? $wht)
         (hashtable-mutable? $eht)
         (hashtable? $imht)
         (hashtable? $imeht)
         (eq-hashtable? $imht)
         (eq-hashtable? $imeht)
         (hashtable-weak? $imht)
         (not (hashtable-ephemeron? $imht))
         (hashtable-ephemeron? $imeht)
         (not (hashtable-weak? $imeht))
         (eq-hashtable-weak? $imht)
         (not (eq-hashtable-ephemeron? $imht))
         (eq-hashtable-ephemeron? $imeht)
         (not (eq-hashtable-weak? $imeht))
         (not (hashtable-mutable? $imht))
         (not (hashtable-mutable? $imeht))
         (hashtable? $wht2)
         (hashtable? $eht2)
         (eq-hashtable? $wht2)
         (eq-hashtable? $eht2)
         (hashtable-weak? $wht2)
         (not (hashtable-ephemeron? $wht2))
         (hashtable-ephemeron? $eht2)
         (not (hashtable-weak? $eht2))
         (eq-hashtable-weak? $wht2)
         (not (eq-hashtable-ephemeron? $ht2))
         (eq-hashtable-ephemeron? $eht2)
         (not (eq-hashtable-weak? $eht2))
         (hashtable-mutable? $wht2)
         (hashtable-mutable? $eht2)))
 ; eq-hashtable-ref
  (error? ; wrong argument count
    (eq-hashtable-ref))
  (error? ; wrong argument count
    (eq-hashtable-ref $wht))
  (error? ; wrong argument count
    (eq-hashtable-ref $wht 'a))
  (error? ; wrong argument count
    (eq-hashtable-ref $wht 'a 'b 'c))
  (error? ; not a hashtable
    (eq-hashtable-ref '(hash . table) 'a 'b))
 ; eq-hashtable-contains?
  (error? ; wrong argument count
    (eq-hashtable-contains?))
  (error? ; wrong argument count
    (eq-hashtable-contains? $wht))
  (error? ; wrong argument count
    (eq-hashtable-contains? $wht 'a 'b))
  (error? ; not a hashtable
    (eq-hashtable-contains? '(hash . table) 'a))
 ; eq-hashtable-set!
  (error? ; wrong argument count
    (eq-hashtable-set!))
  (error? ; wrong argument count
    (eq-hashtable-set! $wht))
  (error? ; wrong argument count
    (eq-hashtable-set! $wht 'a))
  (error? ; wrong argument count
    (eq-hashtable-set! $wht 'a 'b 'c))
  (error? ; not a hashtable
    (eq-hashtable-set! '(hash . table) 'a 'b))
  (error? ; hashtable not mutable
    (eq-hashtable-set! $imht 'a 'b))
 ; eq-hashtable-update!
  (error? ; wrong argument count
    (eq-hashtable-update!))
  (error? ; wrong argument count
    (eq-hashtable-update! $wht))
  (error? ; wrong argument count
    (eq-hashtable-update! $wht 'a values))
  (error? ; wrong argument count
    (eq-hashtable-update! $wht 'a values 'c 'd))
  (error? ; not a hashtable
    (eq-hashtable-update! '(hash . table) 'a values 'b))
  (error? ; hashtable not mutable
    (eq-hashtable-update! $imht 'a values 'b))
  (error? ; not a procedure
    (eq-hashtable-update! $wht 'a "not a procedure" 'b))
 ; eq-hashtable-delete!
  (error? ; wrong argument count
    (eq-hashtable-delete!))
  (error? ; wrong argument count
    (eq-hashtable-delete! $wht))
  (error? ; wrong argument count
    (eq-hashtable-delete! $wht 'a 'b))
  (error? ; not a hashtable
    (eq-hashtable-delete! '(hash . table) 'a))
  (error? ; hashtable not mutable
    (eq-hashtable-delete! $imht 'a))
 ; eq-hashtable-cell
  (error? ; wrong argument count
    (eq-hashtable-cell))
  (error? ; wrong argument count
    (eq-hashtable-cell $wht))
  (error? ; wrong argument count
    (eq-hashtable-cell $wht 'a))
  (error? ; wrong argument count
    (eq-hashtable-cell $wht 'a 'b 'c))
  (error? ; not a hashtable
    (eq-hashtable-cell '(hash . table) 'a 'b))
 ; eq-hashtable-weak?
  (error? ; wrong argument count
    (eq-hashtable-weak?))
  (error? ; wrong argument count
    (eq-hashtable-weak? $ht 3))
  (error? ; not a hashtable
    (eq-hashtable-weak? '(hash . table)))
 ; eq-hashtable-ephemeron?
  (error? ; wrong argument count
    (eq-hashtable-ephemeron?))
  (error? ; wrong argument count
    (eq-hashtable-ephemeron? $ht 3))
  (error? ; not a hashtable
    (eq-hashtable-ephemeron? '(hash . table)))
)

(mat symbol-hashtable-arguments
  (begin
    (define $symht (make-hashtable symbol-hash eq? 50))
    (define $imsymht (hashtable-copy $symht))
    #t)
 ; symbol-hashtable-ref
  (error? ; wrong argument count
    (symbol-hashtable-ref))
  (error? ; wrong argument count
    (symbol-hashtable-ref $symht))
  (error? ; wrong argument count
    (symbol-hashtable-ref $symht 'a))
  (error? ; wrong argument count
    (symbol-hashtable-ref $symht 'a 'b 'c))
  (error? ; not a hashtable
    (symbol-hashtable-ref '(hash . table) 'a 'b))
  (error? ; not a symbol hashtable
    (symbol-hashtable-ref $ht 'a 'b))
  (error? ; not a symbol
    (symbol-hashtable-ref $symht '(a) 'b))
  (error? ; not a symbol
    (hashtable-ref $symht '(a) 'b))
 ; symbol-hashtable-contains?
  (error? ; wrong argument count
    (symbol-hashtable-contains?))
  (error? ; wrong argument count
    (symbol-hashtable-contains? $symht))
  (error? ; wrong argument count
    (symbol-hashtable-contains? $symht 'a 'b))
  (error? ; not a hashtable
    (symbol-hashtable-contains? '(hash . table) 'a))
  (error? ; not a symbol hashtable
    (symbol-hashtable-contains? $ht 'a))
  (error? ; not a symbol
    (symbol-hashtable-contains? $symht '(a)))
  (error? ; not a symbol
    (hashtable-contains? $symht '(a)))
 ; symbol-hashtable-set!
  (error? ; wrong argument count
    (symbol-hashtable-set!))
  (error? ; wrong argument count
    (symbol-hashtable-set! $symht))
  (error? ; wrong argument count
    (symbol-hashtable-set! $symht 'a))
  (error? ; wrong argument count
    (symbol-hashtable-set! $symht 'a 'b 'c))
  (error? ; not a hashtable
    (symbol-hashtable-set! '(hash . table) 'a 'b))
  (error? ; not a symbol hashtable
    (symbol-hashtable-set! $ht 'a 'b))
  (error? ; not a symbol
    (symbol-hashtable-set! $symht '(a) 'b))
  (error? ; not a symbol
    (hashtable-set! $symht '(a) 'b))
  (error? ; hashtable not mutable
    (symbol-hashtable-set! $imsymht 'a 'b))
 ; symbol-hashtable-update!
  (error? ; wrong argument count
    (symbol-hashtable-update!))
  (error? ; wrong argument count
    (symbol-hashtable-update! $symht))
  (error? ; wrong argument count
    (symbol-hashtable-update! $symht 'a values))
  (error? ; wrong argument count
    (symbol-hashtable-update! $symht 'a values 'c 'd))
  (error? ; not a hashtable
    (symbol-hashtable-update! '(hash . table) 'a values 'b))
  (error? ; not a symbol hashtable
    (symbol-hashtable-update! $ht 'a values 'b))
  (error? ; not a symbol
    (symbol-hashtable-update! $symht '(a) values 'b))
  (error? ; not a symbol
    (hashtable-update! $symht '(a) values 'b))
  (error? ; hashtable not mutable
    (symbol-hashtable-update! $imsymht 'a values 'b))
  (error? ; not a procedure
    (symbol-hashtable-update! $symht 'a "not a procedure" 'b))
 ; symbol-hashtable-delete!
  (error? ; wrong argument count
    (symbol-hashtable-delete!))
  (error? ; wrong argument count
    (symbol-hashtable-delete! $symht))
  (error? ; wrong argument count
    (symbol-hashtable-delete! $symht 'a 'b))
  (error? ; not a hashtable
    (symbol-hashtable-delete! '(hash . table) 'a))
  (error? ; not a symbol hashtable
    (symbol-hashtable-delete! $ht 'a))
  (error? ; not a symbol
    (symbol-hashtable-delete! $symht '(a)))
  (error? ; not a symbol
    (hashtable-delete! $symht '(a)))
  (error? ; hashtable not mutable
    (symbol-hashtable-delete! $imsymht 'a))
 ; symbol-hashtable-cell
  (error? ; wrong argument count
    (symbol-hashtable-cell))
  (error? ; wrong argument count
    (symbol-hashtable-cell $symht))
  (error? ; wrong argument count
    (symbol-hashtable-cell $symht 'a))
  (error? ; wrong argument count
    (symbol-hashtable-cell $symht 'a 'b 'c))
  (error? ; not a hashtable
    (symbol-hashtable-cell '(hash . table) 'a 'b))
  (error? ; not a symbol hashtable
    (symbol-hashtable-cell $ht 'a 'b))
  (error? ; not a symbol
    (symbol-hashtable-cell $symht '(a) 'b))
  (error? ; not a symbol
    (hashtable-cell $symht '(a) 'b))
)

(mat eqv-hashtable-arguments
 ; make-weak-eqv-hashtable
  (error? ; wrong argument count
    (make-weak-eqv-hashtable 3 #t))
  (error? ; invalid size
    (make-weak-eqv-hashtable -1))
  (error? ; invalid size
    (make-weak-eqv-hashtable #t))
  (error? ; invalid size
    (make-weak-eqv-hashtable #f))
 ; make-ephemeron-eqv-hashtable
  (error? ; wrong argument count
    (make-ephemeron-eqv-hashtable 3 #t))
  (error? ; invalid size
    (make-ephemeron-eqv-hashtable -1))
  (error? ; invalid size
    (make-ephemeron-eqv-hashtable #t))
  (error? ; invalid size
    (make-ephemeron-eqv-hashtable #f))
)

(mat nonweak-eq-hashtable
  (begin
    (define h (make-eq-hashtable 32))
    (and (hashtable? h)
         (eq-hashtable? h)
         (hashtable-mutable? h)
         (not (eq-hashtable-weak? h))
         (not (eq-hashtable-ephemeron? h))
         (not (hashtable-weak? h))
         (not (hashtable-ephemeron? h))))
  (eq? (hashtable-hash-function h) #f)
  (eq? (hashtable-equivalence-function h) eq?)
  (equal? (hashtable-size h) 0)
  (equal-entries? h '#() '#())
  (eqv? (hashtable-set! h 'a 'aval) (void))
  (equal?
    (list
       (hashtable-contains? h 'a)
       (hashtable-contains? h 'b)
       (hashtable-contains? h 'c))
    '(#t #f #f))
  (eqv? (hashtable-set! h 'b 'bval) (void))
  (equal?
    (list
       (hashtable-contains? h 'a)
       (hashtable-contains? h 'b)
       (hashtable-contains? h 'c))
    '(#t #t #f))
  (eqv? (hashtable-set! h 'c 'cval) (void))
  (equal?
    (list
       (hashtable-contains? h 'a)
       (hashtable-contains? h 'b)
       (hashtable-contains? h 'c))
    '(#t #t #t))
  (equal? (hashtable-size h) 3)
  (equal-entries? h '#(b c a) '#(bval cval aval))
  #;(same-elements? (list->vector (hashtable-map h cons)) '#((a . aval) (b . bval) (c . cval)))
  #;(same-elements?
    (let ([v (make-vector 3)] [i 0])
      (hashtable-for-each h (lambda (key val) (vector-set! v i (cons key val)) (set! i (fx+ i 1))))
      v)
    '#((a . aval) (b . bval) (c . cval)))
  #;(same-elements?
    (let ([v (make-vector 3)] [i 0])
      (hashtable-for-each-cell h (lambda (a) (vector-set! v i a) (set! i (fx+ i 1))))
      v)
    '#((a . aval) (b . bval) (c . cval)))
  (equal? (hashtable-ref h 'a 1) 'aval)
  (equal? (hashtable-ref h 'b #f) 'bval)
  (equal? (hashtable-ref h 'c 'nope) 'cval)
  (eqv? (hashtable-delete! h 'b) (void))
  (equal? (hashtable-size h) 2)
  (equal-entries? h '#(a c) '#(aval cval))
  (begin
    (define h2 (hashtable-copy h #t))
    (and (hashtable? h2)
         (eq-hashtable? h2)
         (hashtable-mutable? h2)
         (not (hashtable-weak? h2))
         (not (eq-hashtable-weak? h2))
         (not (hashtable-ephemeron? h2))
         (not (eq-hashtable-ephemeron? h2))))
  (eq? (hashtable-hash-function h2) #f)
  (eq? (hashtable-equivalence-function h2) eq?)
  (equal? (hashtable-size h2) 2)
  (equal-entries? h2 '#(a c) '#(aval cval))
  (eqv? (hashtable-clear! h 4) (void))
  (equal?
    (list
      (hashtable-size h)
      (hashtable-ref h 'a 1)
      (hashtable-ref h 'b #f)
      (hashtable-ref h 'c 'nope))
   '(0 1 #f nope))
  (equal-entries? h '#() '#())
  (equal?
    (list
      (hashtable-size h2)
      (hashtable-ref h2 'a 1)
      (hashtable-ref h2 'b #f)
      (hashtable-ref h2 'c 'nope))
    '(2 aval #f cval))
  (equal-entries? h2 '#(a c) '#(aval cval))
  (eqv?
    (hashtable-update! h 'q
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (hashtable-ref h 'q #f) 18)
  (eqv?
    (hashtable-update! h 'q
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (hashtable-ref h 'q #f) 19)
  (equal? (hashtable-size h) 1)
 ; test hashtable-copy when some keys may have moved
  (let ([t (parameterize ([collect-request-handler void])
             (let ([h4a (make-eq-hashtable 32)]
                   [k* (map list (make-list 100))])
               (for-each (lambda (x) (hashtable-set! h4a x x)) k*)
               (collect)
              ; create copy after collection but before otherwise touching h4a
               (let ([h4b (hashtable-copy h4a #t)])
                 (andmap
                   (lambda (k) (eq? (hashtable-ref h4b k #f) k))
                   k*))))])
    (collect)
    t)

 ; test for proper shrinkage
  (eqv?
    (let ([ht (make-eq-hashtable 32)])
      (for-each
        (lambda (k*) (for-each (lambda (k) (hashtable-delete! ht k)) k*))
        (let ([k** (map (lambda (x) (map list (make-list 1000)))
                        (make-list 100))])
          (for-each
            (lambda (k*) (map (lambda (k) (hashtable-set! ht k 75)) k*))
            k**)
          k**))
      (#%$hashtable-veclen ht))
    32)
)

(mat weak-eq-hashtable
  (begin
    (define ka (list 'a))
    (define kb (list 'b))
    (define kc (list 'c))
    (define kq (list 'q))
    (define ky (list 'y))
    (define kz (list 'z))
    #t)
  (begin
    (define h (make-weak-eq-hashtable 32))
    (and (hashtable? h)
         (eq-hashtable? h)
         (hashtable-mutable? h)
         (hashtable-weak? h)
         (eq-hashtable-weak? h)))
  (eq? (hashtable-hash-function h) #f)
  (eq? (hashtable-equivalence-function h) eq?)
  (equal? (hashtable-size h) 0)
  (equal-entries? h '#() '#())
  (eqv? (hashtable-set! h ka 'aval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc))
    '(#t #f #f))
  (eqv? (hashtable-set! h kb 'bval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc))
    '(#t #t #f))
  (eqv? (hashtable-set! h kc 'cval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc))
    '(#t #t #t))
  (equal? (hashtable-size h) 3)
  (equal-entries? h '#((a) (b) (c)) '#(aval bval cval))
  (andmap weak-pair? (vector->list (hashtable-cells h)))
  #;(same-elements? (list->vector (hashtable-map h cons)) '#(((a) . aval) ((b) . bval) ((c) . cval)))
  #;(same-elements?
    (let ([v (make-vector 3)] [i 0])
      (hashtable-for-each h (lambda (key val) (vector-set! v i (cons key val)) (set! i (fx+ i 1))))
      v)
    '#(((a) . aval) ((b) . bval) ((c) . cval)))
  #;(same-elements?
    (let ([v (make-vector 3)] [i 0])
      (hashtable-for-each-cell h (lambda (a) (vector-set! v i a) (set! i (fx+ i 1))))
      v)
    '#(((a) . aval) ((b) . bval) ((c) . cval)))
  (equal? (hashtable-ref h ka 1) 'aval)
  (equal? (hashtable-ref h kb #f) 'bval)
  (equal? (hashtable-ref h kc 'nope) 'cval)
  (eqv? (hashtable-delete! h kb) (void))
  (equal? (hashtable-size h) 2)
  (equal-entries? h '#((a) (c)) '#(aval cval))
  (begin
    (define h2 (hashtable-copy h #t))
    (and (hashtable? h2)
         (eq-hashtable? h2)
         (hashtable-mutable? h2)
         (eq-hashtable-weak? h2)
         (hashtable-weak? h2)))
  (eq? (hashtable-hash-function h2) #f)
  (eq? (hashtable-equivalence-function h2) eq?)
  (equal? (hashtable-size h2) 2)
  (equal-entries? h2 '#((a) (c)) '#(aval cval))
  (eqv? (hashtable-clear! h 4) (void))
  (equal?
    (list
      (hashtable-size h)
      (hashtable-ref h ka 1)
      (hashtable-ref h kb #f)
      (hashtable-ref h kc 'nope))
   '(0 1 #f nope))
  (equal-entries? h '#() '#())
  (equal?
    (list
      (hashtable-size h2)
      (hashtable-ref h2 ka 1)
      (hashtable-ref h2 kb #f)
      (hashtable-ref h2 kc 'nope))
    '(2 aval #f cval))
  (equal-entries? h2 '#((a) (c)) '#(aval cval))
  (eqv?
    (hashtable-update! h kq
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (hashtable-ref h kq #f) 18)
  (eqv?
    (hashtable-update! h kq
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (hashtable-ref h kq #f) 19)
  (equal? (hashtable-size h) 1)
  (equal-entries? h '#((q)) '#(19))
  (eqv?
    (begin
      (set! kq (void))
      (collect (collect-maximum-generation))
      (hashtable-size h))
    0)
  (equal-entries? h '#() '#())
  #;(eqv? (hashtable-map h (lambda args (error #f "oops"))) '())
  #;(eqv? (hashtable-for-each h (lambda args (error #f "oops"))) (void))
  #;(eqv? (hashtable-for-each-cell h (lambda args (error #f "oops"))) (void))
  (equal? (hashtable-ref h ky #f) #f)
  (eqv?
    (hashtable-set! h ky 'toad)
    (void))
  (equal? (hashtable-ref h ky #f) 'toad)
  (equal? (hashtable-ref h kz #f) #f)
  (eqv?
    (hashtable-update! h kz list 'frog)
    (void))
  (equal? (hashtable-ref h kz #f) '(frog))
  (equal-entries?
    h
    (vector kz ky)
    (vector (hashtable-ref h kz #f) 'toad))
  (eqv? (hashtable-ref h '(zippo) 'nil) 'nil)
  (begin
    (define h3 (hashtable-copy h2 #f))
    (and (hashtable? h3)
         (eq-hashtable? h3)
         (not (hashtable-mutable? h3))
         (eq-hashtable-weak? h3)
         (hashtable-weak? h3)))
  (equal-entries? h2 '#((a) (c)) '#(aval cval))
  (equal-entries? h3 '#((a) (c)) '#(aval cval))
  (equal?
    (begin
      (set! ka (void))
      (collect (collect-maximum-generation))
      (list (hashtable-size h2) (hashtable-size h3)))
    '(1 1))
  (equal-entries? h2 '#((c)) '#(cval))
  (equal-entries? h3 '#((c)) '#(cval))
  (eqv?
    (begin
      (set! h3 (void))
      (collect (collect-maximum-generation))
      (hashtable-size h2))
    1)
  (equal-entries? h2 '#((c)) '#(cval))

 ; test for proper shrinkage
  (eqv?
    (let ([ht (make-eq-hashtable 32)])
      (for-each
        (lambda (k*) (for-each (lambda (k) (hashtable-delete! ht k)) k*))
        (let ([k** (map (lambda (x) (map list (make-list 1000)))
                        (make-list 100))])
          (for-each
            (lambda (k*) (map (lambda (k) (hashtable-set! ht k 75)) k*))
            k**)
          k**))
      (#%$hashtable-veclen ht))
    32)

 ; test for proper shrinkage as objects are bwp'd
 ; uses delete to trigger final shrinkage
  (equal?
    (let* ([ht (make-weak-eq-hashtable 32)]
           [len (#%$hashtable-veclen ht)])
      (hashtable-set! ht 'a 'b)
      (for-each
        (lambda (k*) (map (lambda (k) (hashtable-set! ht k 75)) k*))
        (map (lambda (x) (map list (make-list 1000))) (make-list 100)))
      (collect (collect-maximum-generation))
      (hashtable-delete! ht 'a)
      (list (hashtable-size ht) (= (#%$hashtable-veclen ht) len)))
    '(0 #t))

  ; test that weak-hashtable values *do* make keys reachable
  (let ([wk1 (list 1)]
        [wk2 (list 2)]
        [wk3 (list 3)]
        [wk4 (list 4)]
        [ht (make-weak-eq-hashtable)])
    (hashtable-set! ht wk1 wk1)
    (hashtable-set! ht wk2 wk1)
    (hashtable-set! ht wk3 wk3)
    (hashtable-set! ht wk4 wk2)
    (collect (collect-maximum-generation))
    (and
     (equal-entries? ht '#((1) (2) (3) (4)) '#((1) (1) (3) (2)))
     (equal? (hashtable-ref ht wk1 #f) wk1)
     (equal? (hashtable-ref ht wk2 #f) wk1)
     (equal? (hashtable-ref ht wk3 #f) wk3)
     (equal? (hashtable-ref ht wk4 #f) wk2)
     (begin
       (set! wk1 #f)
       (set! wk2 #f)
       (set! wk3 #f)
       (collect (collect-maximum-generation))
       (and
        (equal-entries? ht '#((1) (2) (3) (4)) '#((1) (1) (3) (2)))
        (equal? (hashtable-ref ht wk4 #f) '(2))
        (begin
          (set! wk4 #f)
          (collect (collect-maximum-generation))
          (equal-entries? ht '#((1) (2) (3)) '#((1) (1) (3))))))))
)

(mat ephemeron-eq-hashtable
  (begin
    (define ka (list 'a)) ; will map to self  \ Doesn't do anything to check
    (define kb (list 'b)) ; will map to kc \  | ephemeronness, but just in
    (define kc (list 'c)) ; will map to kb /  / case.
    (define kq (list 'q))
    (define ky (list 'y))
    (define kz (list 'z))
    #t)
  (begin
    (define h (make-ephemeron-eq-hashtable 32))
    (and (hashtable? h)
         (eq-hashtable? h)
         (hashtable-mutable? h)
         (hashtable-ephemeron? h)
         (eq-hashtable-ephemeron? h)))
  (eq? (hashtable-hash-function h) #f)
  (eq? (hashtable-equivalence-function h) eq?)
  (equal? (hashtable-size h) 0)
  (equal-entries? h '#() '#())
  (eqv? (hashtable-set! h ka ka) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc))
    '(#t #f #f))
  (eqv? (hashtable-set! h kb kc) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc))
    '(#t #t #f))
  (eqv? (hashtable-set! h kc kb) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc))
    '(#t #t #t))
  (equal? (hashtable-size h) 3)
  (equal-entries? h '#((a) (b) (c)) '#((a) (c) (b)))
  (andmap ephemeron-pair? (vector->list (hashtable-cells h)))
  #;(same-elements? (list->vector (hashtable-map h cons)) '#(((a) . a) ((b) . c) ((c) . b)))
  #;(same-elements?
    (let ([v (make-vector 3)] [i 0])
      (hashtable-for-each h (lambda (key val) (vector-set! v i (cons key val)) (set! i (fx+ i 1))))
      v)
    '#(((a) . a) ((b) . c) ((c) . b)))
  #;(same-elements?
    (let ([v (make-vector 3)] [i 0])
      (hashtable-for-each-cell h (lambda (a) (vector-set! v i a) (set! i (fx+ i 1))))
      v)
    '#(((a) . a) ((b) . c) ((c) . b)))
  (equal? (hashtable-ref h ka 1) '(a))
  (equal? (hashtable-ref h kb #f) '(c))
  (equal? (hashtable-ref h kc 'nope) '(b))
  (eqv? (hashtable-delete! h kb) (void))
  (equal? (hashtable-size h) 2)
  (equal-entries? h '#((a) (c)) '#((a) (b)))
  (begin
    (define h2 (hashtable-copy h #t))
    (and (hashtable? h2)
         (eq-hashtable? h2)
         (hashtable-mutable? h2)
         (eq-hashtable-ephemeron? h2)
         (hashtable-ephemeron? h2)))
  (eq? (hashtable-hash-function h2) #f)
  (eq? (hashtable-equivalence-function h2) eq?)
  (equal? (hashtable-size h2) 2)
  (equal-entries? h2 '#((a) (c)) '#((a) (b)))
  (eqv? (hashtable-clear! h 4) (void))
  (equal?
    (list
      (hashtable-size h)
      (hashtable-ref h ka 1)
      (hashtable-ref h kb #f)
      (hashtable-ref h kc 'nope))
   '(0 1 #f nope))
  (equal-entries? h '#() '#())
  (equal?
    (list
      (hashtable-size h2)
      (hashtable-ref h2 ka 1)
      (hashtable-ref h2 kb #f)
      (hashtable-ref h2 kc 'nope))
    '(2 (a) #f (b)))
  (equal-entries? h2 '#((a) (c)) '#((a) (b)))
  (eqv?
    (hashtable-update! h kq
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (hashtable-ref h kq #f) 18)
  (eqv?
    (hashtable-update! h kq
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (hashtable-ref h kq #f) 19)
  (equal? (hashtable-size h) 1)
  (equal-entries? h '#((q)) '#(19))
  (eqv?
    (begin
      (set! kq (void))
      (collect (collect-maximum-generation))
      (hashtable-size h))
    0)
  (equal-entries? h '#() '#())
  #;(eqv? (hashtable-map h (lambda args (error #f "oops"))) '())
  #;(eqv? (hashtable-for-each h (lambda args (error #f "oops"))) (void))
  #;(eqv? (hashtable-for-each-cell h (lambda args (error #f "oops"))) (void))
  (equal? (hashtable-ref h ky #f) #f)
  (eqv?
    (hashtable-set! h ky 'toad)
    (void))
  (equal? (hashtable-ref h ky #f) 'toad)
  (equal? (hashtable-ref h kz #f) #f)
  (eqv?
    (hashtable-update! h kz list 'frog)
    (void))
  (equal? (hashtable-ref h kz #f) '(frog))
  (equal-entries?
    h
    (vector kz ky)
    (vector (hashtable-ref h kz #f) 'toad))
  (eqv? (hashtable-ref h '(zippo) 'nil) 'nil)
  (begin
    (define h3 (hashtable-copy h2 #f))
    (and (hashtable? h3)
         (eq-hashtable? h3)
         (not (hashtable-mutable? h3))
         (eq-hashtable-ephemeron? h3)
         (hashtable-ephemeron? h3)))
  (equal-entries? h2 '#((a) (c)) '#((a) (b)))
  (equal-entries? h3 '#((a) (c)) '#((a) (b)))
  (equal?
    (begin
      (set! ka (void))
      (collect (collect-maximum-generation))
      (list (hashtable-size h2) (hashtable-size h3)))
    '(1 1))
  (equal-entries? h2 '#((c)) '#((b)))
  (equal-entries? h3 '#((c)) '#((b)))
  (eqv?
    (begin
      (set! h3 (void))
      (collect (collect-maximum-generation))
      (hashtable-size h2))
    1)
  (equal-entries? h2 '#((c)) '#((b)))

 ; test for proper shrinkage
  (eqv?
    (let ([ht (make-eq-hashtable 32)])
      (for-each
        (lambda (k*) (for-each (lambda (k) (hashtable-delete! ht k)) k*))
        (let ([k** (map (lambda (x) (map list (make-list 1000)))
                        (make-list 100))])
          (for-each
            (lambda (k*) (map (lambda (k) (hashtable-set! ht k 75)) k*))
            k**)
          k**))
      (#%$hashtable-veclen ht))
    32)

 ; test for proper shrinkage as objects are bwp'd
 ; uses delete to trigger final shrinkage
  (equal?
    (let* ([ht (make-ephemeron-eq-hashtable 32)]
           [len (#%$hashtable-veclen ht)])
      (hashtable-set! ht 'a 'b)
      (for-each
        (lambda (k*) (map (lambda (k) (hashtable-set! ht k 75)) k*))
        (map (lambda (x) (map list (make-list 1000))) (make-list 100)))
      (collect (collect-maximum-generation))
      (hashtable-delete! ht 'a)
      (list (hashtable-size ht) (= (#%$hashtable-veclen ht) len)))
    '(0 #t))

  ; test that ephemeron-hashtable values don't make keys reachable
  (let ([wk1 (list 1)]
        [wk2 (list 2)]
        [wk3 (list 3)]
        [wk4 (list 4)]
        [ht (make-ephemeron-eq-hashtable)])
    (hashtable-set! ht wk1 wk1)
    (hashtable-set! ht wk2 wk1)
    (hashtable-set! ht wk3 wk3)
    (hashtable-set! ht wk4 wk2)
    (collect (collect-maximum-generation))
    (and
     (equal-entries? ht '#((1) (2) (3) (4)) '#((1) (1) (3) (2)))
     (equal? (hashtable-ref ht wk1 #f) wk1)
     (equal? (hashtable-ref ht wk2 #f) wk1)
     (equal? (hashtable-ref ht wk3 #f) wk3)
     (equal? (hashtable-ref ht wk4 #f) wk2)
     (begin
       (set! wk1 #f)
       (set! wk2 #f)
       (set! wk3 #f)
       (collect (collect-maximum-generation))
       (and
        (equal-entries? ht '#((1) (2) (4)) '#((1) (1) (2)))
        (equal? (hashtable-ref ht wk4 #f) '(2))
        (begin
          (set! wk4 #f)
          (collect (collect-maximum-generation))
          (equal-entries? ht '#() '#()))))))
)

(mat eq-hashtable-cell
  (let ()
    (define-record fribble (x))
    (define random-object
      (lambda (x)
        (case (random 9)
          [(0) (cons 'a 'b)]
          [(1) (vector 'c)]
          [(2) (string #\a #\b)]
          [(3) (make-fribble 'q)]
          [(4) (gensym)]
          [(5) (open-output-string)]
          [(6) (fxvector 15 55)]
          [(7) (lambda () x)]
          [else (box 'top)])))
    (let ([ls1 (let f ([n 10000])
                 (if (fx= n 0)
                     '()
                     (cons
                       (cons (random-object 4) (random-object 7))
                       (f (fx- n 1)))))]
          [ht (make-eq-hashtable)]
          [wht (make-weak-eq-hashtable)]
          [eht (make-ephemeron-eq-hashtable)])
      (let ([ls2 (map (lambda (a1) (eq-hashtable-cell ht (car a1) (cdr a1))) ls1)]
            [ls3 (map (lambda (a1) (hashtable-cell wht (car a1) (cdr a1))) ls1)]
            [ls4 (map (lambda (a1) (hashtable-cell eht (car a1) (cdr a1))) ls1)])
        (unless (andmap (lambda (a1 a2 a3 a4)
                          (and (eq? (car a1) (car a2))
                               (eq? (car a2) (car a3))
                               (eq? (car a2) (car a4))))
                        ls1 ls2 ls3 ls4)
          (errorf #f "keys are not eq"))
        (unless (andmap (lambda (a1 a2 a3 a4)
                          (and (eq? (cdr a1) (cdr a2))
                               (eq? (cdr a2) (cdr a3))
                               (eq? (cdr a2) (cdr a4))))
                        ls1 ls2 ls3 ls4)
          (errorf #f "values are not eq"))
        (for-each (lambda (a1)
                    (let ([o (random-object 3)])
                      ;; Value refers to key:
                      (hashtable-set! eht o (list o (car a1)))))
                  ls1)
        (for-each
          (lambda (a1)
            (when (fx< (random 10) 5)
              (set-car! a1 #f)))
          ls1)
        (let loop ([i (min (expt (collect-generation-radix) (collect-maximum-generation)) 1000)])
          (unless (fx= i 0)
            (collect)
            (unless (andmap (lambda (a2 a3 a4) (and (eq? (car a2) (car a3)) (eq? (car a2) (car a4))))
                            ls2 ls3 ls4)
              (errorf #f "a2/a3/a4 keys not eq after collection"))
            (unless (and (andmap (lambda (a3) (not (bwp-object? (car a3)))) ls3)
                         (andmap (lambda (a4) (not (bwp-object? (car a4)))) ls4))
              (errorf #f "keys have been bwp'd"))
            (loop (fx- i 1))))
        (for-each
          (lambda (a2)
            (hashtable-delete! ht (car a2))
            (set-car! a2 #f))
          ls2)
        (unless (and (equal? (hashtable-keys ht) '#())
                     (equal? (hashtable-values ht) '#())
                     (zero? (hashtable-size ht)))
          (errorf #f "ht has not been cleared out"))
        (let loop ([i (min (expt (collect-generation-radix) (collect-maximum-generation)) 1000)])
          (unless (fx= i 0)
            (collect)
            (unless (andmap (lambda (a1 a3 a4)
                              (or (not (car a1))
                                  (and (eq? (car a1) (car a3))
                                       (eq? (car a1) (car a4)))))
                            ls1 ls3 ls4)
              (errorf #f "a1/a3/a4 keys not eq after collection"))
            (loop (fx- i 1))))
        (for-each
          (lambda (a1 a3 a4)
            (unless (or (car a1)
                        (and (bwp-object? (car a3))
                             (bwp-object? (car a4))))
              (errorf #f "~s has not been bwp'd I" (car a3))))
          ls1 ls3 ls4)
        (for-each (lambda (a1) (set-car! a1 #f)) ls1)
        (collect (collect-maximum-generation))
        (unless (and (andmap (lambda (a3) (bwp-object? (car a3))) ls3)
                     (andmap (lambda (a4) (bwp-object? (car a4))) ls4))
          (errorf #f "keys have not been bwp'd II"))
        (unless (and (equal? (hashtable-keys wht) '#())
                     (equal? (hashtable-values wht) '#())
                     (zero? (hashtable-size wht)))
          (errorf #f "wht has not been cleared out"))
        (unless (and (equal? (hashtable-keys eht) '#())
                     (equal? (hashtable-values eht) '#())
                     (zero? (hashtable-size eht)))
          (errorf #f "eht has not been cleared out"))))
    #t)
)

(mat $nonweak-eq-hashtable
  (begin
    (define h (make-eq-hashtable 32))
    (and (hashtable? h)
         (eq-hashtable? h)
         (hashtable-mutable? h)
         (not (eq-hashtable-weak? h))
         (not (hashtable-weak? h))
         (not (eq-hashtable-ephemeron? h))
         (not (hashtable-ephemeron? h))))
  (eq? (hashtable-hash-function h) #f)
  (eq? (hashtable-equivalence-function h) eq?)
  (equal? (hashtable-size h) 0)
  (equal-entries? h '#() '#())
  (eqv? (eq-hashtable-set! h 'a 'aval) (void))
  (equal?
    (list
       (eq-hashtable-contains? h 'a)
       (eq-hashtable-contains? h 'b)
       (eq-hashtable-contains? h 'c))
    '(#t #f #f))
  (eqv? (eq-hashtable-set! h 'b 'bval) (void))
  (equal?
    (list
       (eq-hashtable-contains? h 'a)
       (eq-hashtable-contains? h 'b)
       (eq-hashtable-contains? h 'c))
    '(#t #t #f))
  (eqv? (eq-hashtable-set! h 'c 'cval) (void))
  (equal?
    (list
       (eq-hashtable-contains? h 'a)
       (eq-hashtable-contains? h 'b)
       (eq-hashtable-contains? h 'c))
    '(#t #t #t))
  (equal? (hashtable-size h) 3)
  (equal-entries? h '#(b c a) '#(bval cval aval))
  (equal? (eq-hashtable-ref h 'a 1) 'aval)
  (equal? (eq-hashtable-ref h 'b #f) 'bval)
  (equal? (eq-hashtable-ref h 'c 'nope) 'cval)
  (eqv? (eq-hashtable-delete! h 'b) (void))
  (equal? (hashtable-size h) 2)
  (equal-entries? h '#(a c) '#(aval cval))
  (begin
    (define h2 (hashtable-copy h #t))
    (and (hashtable? h2)
         (eq-hashtable? h2)
         (hashtable-mutable? h2)
         (not (eq-hashtable-weak? h2))
         (not (hashtable-weak? h2))))
  (equal? (hashtable-size h2) 2)
  (equal-entries? h2 '#(a c) '#(aval cval))
  (eqv? (hashtable-clear! h 4) (void))
  (equal?
    (list
      (hashtable-size h)
      (eq-hashtable-ref h 'a 1)
      (eq-hashtable-ref h 'b #f)
      (eq-hashtable-ref h 'c 'nope))
   '(0 1 #f nope))
  (equal-entries? h '#() '#())
  (equal?
    (list
      (hashtable-size h2)
      (eq-hashtable-ref h2 'a 1)
      (eq-hashtable-ref h2 'b #f)
      (eq-hashtable-ref h2 'c 'nope))
    '(2 aval #f cval))
  (equal-entries? h2 '#(a c) '#(aval cval))
  (eqv?
    (eq-hashtable-update! h 'q
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (eq-hashtable-ref h 'q #f) 18)
  (eqv?
    (eq-hashtable-update! h 'q
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (eq-hashtable-ref h 'q #f) 19)
  (equal? (hashtable-size h) 1)
 ; test hashtable-copy when some keys may have moved
  (let ([t (parameterize ([collect-request-handler void])
             (let ([h4a (make-eq-hashtable 32)]
                   [k* (map list (make-list 100))])
               (for-each (lambda (x) (eq-hashtable-set! h4a x x)) k*)
               (collect)
              ; create copy after collection but before otherwise touching h4a
               (let ([h4b (hashtable-copy h4a #t)])
                 (andmap
                   (lambda (k) (eq? (eq-hashtable-ref h4b k #f) k))
                   k*))))])
    (collect)
    t)

 ; test for proper shrinkage, etc.
  (equal?
    (let* ([ht (make-eq-hashtable)] [minlen (#%$hashtable-veclen ht)])
      (define power-of-two? (lambda (n) (fx= (fxbit-count n) 1)))
      (let f ([i 0])
        (unless (fx= i (expt 2 17))
          (let ([k (fx* i 2)])
            (eq-hashtable-set! ht k i)
            (f (fx+ i 1))
            (assert (eq-hashtable-contains? ht k))
            (assert (power-of-two? (#%$hashtable-veclen ht)))
            (eq-hashtable-delete! ht k))))
      (list (hashtable-size ht) (fx= (#%$hashtable-veclen ht) minlen)))
    '(0 #t))

  (equal?
    (let ([ht (make-eq-hashtable 32)])
      (define power-of-two? (lambda (n) (fx= (fxbit-count n) 1)))
      (let f ([i 0])
        (unless (fx= i (expt 2 17))
          (let ([k (fx* i 2)])
            (eq-hashtable-set! ht k i)
            (f (fx+ i 1))
            (assert (eq-hashtable-contains? ht k))
            (assert (power-of-two? (#%$hashtable-veclen ht)))
            (eq-hashtable-delete! ht k))))
      (list (hashtable-size ht) (#%$hashtable-veclen ht)))
    '(0 32))
)

(mat $weak-eq-hashtable
  (begin
    (define ka (list 'a))
    (define kb (list 'b))
    (define kc (list 'c))
    (define kq (list 'q))
    (define ky (list 'y))
    (define kz (list 'z))
    #t)
  (begin
    (define h (make-weak-eq-hashtable 32))
    (and (hashtable? h)
         (eq-hashtable? h)
         (hashtable-mutable? h)
         (eq-hashtable-weak? h)
         (hashtable-weak? h)))
  (eq? (hashtable-hash-function h) #f)
  (eq? (hashtable-equivalence-function h) eq?)
  (equal? (hashtable-size h) 0)
  (equal-entries? h '#() '#())
  (eqv? (eq-hashtable-set! h ka 'aval) (void))
  (equal?
    (list
       (eq-hashtable-contains? h ka)
       (eq-hashtable-contains? h kb)
       (eq-hashtable-contains? h kc))
    '(#t #f #f))
  (eqv? (eq-hashtable-set! h kb 'bval) (void))
  (equal?
    (list
       (eq-hashtable-contains? h ka)
       (eq-hashtable-contains? h kb)
       (eq-hashtable-contains? h kc))
    '(#t #t #f))
  (eqv? (eq-hashtable-set! h kc 'cval) (void))
  (equal?
    (list
       (eq-hashtable-contains? h ka)
       (eq-hashtable-contains? h kb)
       (eq-hashtable-contains? h kc))
    '(#t #t #t))
  (equal? (hashtable-size h) 3)
  (equal-entries? h '#((a) (b) (c)) '#(aval bval cval))
  (andmap weak-pair? (vector->list (hashtable-cells h)))
  (equal? (eq-hashtable-ref h ka 1) 'aval)
  (equal? (eq-hashtable-ref h kb #f) 'bval)
  (equal? (eq-hashtable-ref h kc 'nope) 'cval)
  (eqv? (eq-hashtable-delete! h kb) (void))
  (equal? (hashtable-size h) 2)
  (equal-entries? h '#((a) (c)) '#(aval cval))
  (begin
    (define h2 (hashtable-copy h #t))
    (and (hashtable? h2)
         (eq-hashtable? h2)
         (hashtable-mutable? h2)
         (hashtable-weak? h2)
         (eq-hashtable-weak? h2)))
  (equal? (hashtable-size h2) 2)
  (equal-entries? h2 '#((a) (c)) '#(aval cval))
  (eqv? (hashtable-clear! h 4) (void))
  (equal?
    (list
      (hashtable-size h)
      (eq-hashtable-ref h ka 1)
      (eq-hashtable-ref h kb #f)
      (eq-hashtable-ref h kc 'nope))
   '(0 1 #f nope))
  (equal-entries? h '#() '#())
  (equal?
    (list
      (hashtable-size h2)
      (eq-hashtable-ref h2 ka 1)
      (eq-hashtable-ref h2 kb #f)
      (eq-hashtable-ref h2 kc 'nope))
    '(2 aval #f cval))
  (equal-entries? h2 '#((a) (c)) '#(aval cval))
  (eqv?
    (eq-hashtable-update! h kq
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (eq-hashtable-ref h kq #f) 18)
  (eqv?
    (eq-hashtable-update! h kq
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (eq-hashtable-ref h kq #f) 19)
  (equal? (hashtable-size h) 1)
  (equal-entries? h '#((q)) '#(19))
  (eqv?
    (begin
      (set! kq (void))
      (collect (collect-maximum-generation))
      (hashtable-size h))
    0)
  (equal-entries? h '#() '#())
  (equal? (eq-hashtable-ref h ky #f) #f)
  (eqv?
    (eq-hashtable-set! h ky 'toad)
    (void))
  (equal? (eq-hashtable-ref h ky #f) 'toad)
  (equal? (eq-hashtable-ref h kz #f) #f)
  (eqv?
    (eq-hashtable-update! h kz list 'frog)
    (void))
  (equal? (eq-hashtable-ref h kz #f) '(frog))
  (equal-entries?
    h
    (vector kz ky)
    (vector (eq-hashtable-ref h kz #f) 'toad))
  (eqv? (eq-hashtable-ref h '(zippo) 'nil) 'nil)
  (begin
    (define h3 (hashtable-copy h2 #f))
    (and (hashtable? h3)
         (eq-hashtable? h3)
         (not (hashtable-mutable? h3))
         (eq-hashtable-weak? h3)
         (hashtable-weak? h3)))
  (equal-entries? h2 '#((a) (c)) '#(aval cval))
  (equal-entries? h3 '#((a) (c)) '#(aval cval))
  (equal?
    (begin
      (set! ka (void))
      (collect (collect-maximum-generation))
      (list (hashtable-size h2) (hashtable-size h3)))
    '(1 1))
  (equal-entries? h2 '#((c)) '#(cval))
  (equal-entries? h3 '#((c)) '#(cval))
  (eqv?
    (begin
      (set! h3 (void))
      (collect (collect-maximum-generation))
      (hashtable-size h2))
    1)
  (equal-entries? h2 '#((c)) '#(cval))

 ; test for proper shrinkage
  (eqv?
    (let ([ht (make-eq-hashtable 32)])
      (for-each
        (lambda (k*) (for-each (lambda (k) (eq-hashtable-delete! ht k)) k*))
        (let ([k** (map (lambda (x) (map list (make-list 1000)))
                        (make-list 100))])
          (for-each
            (lambda (k*) (map (lambda (k) (eq-hashtable-set! ht k 75)) k*))
            k**)
          k**))
      (#%$hashtable-veclen ht))
    32)

 ; test for proper shrinkage as objects are bwp'd
 ; uses delete to trigger final shrinkage
  (equal?
    (let* ([ht (make-weak-eq-hashtable 32)]
           [len (#%$hashtable-veclen ht)])
      (eq-hashtable-set! ht 'a 'b)
      (for-each
        (lambda (k*) (map (lambda (k) (eq-hashtable-set! ht k 75)) k*))
        (map (lambda (x) (map list (make-list 1000))) (make-list 100)))
      (collect (collect-maximum-generation))
      (eq-hashtable-delete! ht 'a)
      (list (hashtable-size ht) (= (#%$hashtable-veclen ht) len)))
    '(0 #t))
  )

(mat $ephemeron-eq-hashtable
  (begin
    (define ka (list 'a))
    (define kb (list 'b))
    (define kc (list 'c))
    (define kq (list 'q))
    (define ky (list 'y))
    (define kz (list 'z))
    #t)
  (begin
    (define h (make-ephemeron-eq-hashtable 32))
    (and (hashtable? h)
         (eq-hashtable? h)
         (hashtable-mutable? h)
         (eq-hashtable-ephemeron? h)
         (hashtable-ephemeron? h)))
  (eq? (hashtable-hash-function h) #f)
  (eq? (hashtable-equivalence-function h) eq?)
  (equal? (hashtable-size h) 0)
  (equal-entries? h '#() '#())
  (eqv? (eq-hashtable-set! h ka 'aval) (void))
  (equal?
    (list
       (eq-hashtable-contains? h ka)
       (eq-hashtable-contains? h kb)
       (eq-hashtable-contains? h kc))
    '(#t #f #f))
  (eqv? (eq-hashtable-set! h kb 'bval) (void))
  (equal?
    (list
       (eq-hashtable-contains? h ka)
       (eq-hashtable-contains? h kb)
       (eq-hashtable-contains? h kc))
    '(#t #t #f))
  (eqv? (eq-hashtable-set! h kc 'cval) (void))
  (equal?
    (list
       (eq-hashtable-contains? h ka)
       (eq-hashtable-contains? h kb)
       (eq-hashtable-contains? h kc))
    '(#t #t #t))
  (equal? (hashtable-size h) 3)
  (equal-entries? h '#((a) (b) (c)) '#(aval bval cval))
  (andmap ephemeron-pair? (vector->list (hashtable-cells h)))
  (equal? (eq-hashtable-ref h ka 1) 'aval)
  (equal? (eq-hashtable-ref h kb #f) 'bval)
  (equal? (eq-hashtable-ref h kc 'nope) 'cval)
  (eqv? (eq-hashtable-delete! h kb) (void))
  (equal? (hashtable-size h) 2)
  (equal-entries? h '#((a) (c)) '#(aval cval))
  (begin
    (define h2 (hashtable-copy h #t))
    (and (hashtable? h2)
         (eq-hashtable? h2)
         (hashtable-mutable? h2)
         (hashtable-ephemeron? h2)
         (eq-hashtable-ephemeron? h2)))
  (equal? (hashtable-size h2) 2)
  (equal-entries? h2 '#((a) (c)) '#(aval cval))
  (eqv? (hashtable-clear! h 4) (void))
  (equal?
    (list
      (hashtable-size h)
      (eq-hashtable-ref h ka 1)
      (eq-hashtable-ref h kb #f)
      (eq-hashtable-ref h kc 'nope))
   '(0 1 #f nope))
  (equal-entries? h '#() '#())
  (equal?
    (list
      (hashtable-size h2)
      (eq-hashtable-ref h2 ka 1)
      (eq-hashtable-ref h2 kb #f)
      (eq-hashtable-ref h2 kc 'nope))
    '(2 aval #f cval))
  (equal-entries? h2 '#((a) (c)) '#(aval cval))
  (eqv?
    (eq-hashtable-update! h kq
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (eq-hashtable-ref h kq #f) 18)
  (eqv?
    (eq-hashtable-update! h kq
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (eq-hashtable-ref h kq #f) 19)
  (equal? (hashtable-size h) 1)
  (equal-entries? h '#((q)) '#(19))
  (eqv?
    (begin
      (set! kq (void))
      (collect (collect-maximum-generation))
      (hashtable-size h))
    0)
  (equal-entries? h '#() '#())
  (equal? (eq-hashtable-ref h ky #f) #f)
  (eqv?
    (eq-hashtable-set! h ky 'toad)
    (void))
  (equal? (eq-hashtable-ref h ky #f) 'toad)
  (equal? (eq-hashtable-ref h kz #f) #f)
  (eqv?
    (eq-hashtable-update! h kz list 'frog)
    (void))
  (equal? (eq-hashtable-ref h kz #f) '(frog))
  (equal-entries?
    h
    (vector kz ky)
    (vector (eq-hashtable-ref h kz #f) 'toad))
  (eqv? (eq-hashtable-ref h '(zippo) 'nil) 'nil)
  (begin
    (define h3 (hashtable-copy h2 #f))
    (and (hashtable? h3)
         (eq-hashtable? h3)
         (not (hashtable-mutable? h3))
         (eq-hashtable-ephemeron? h3)
         (hashtable-ephemeron? h3)))
  (equal-entries? h2 '#((a) (c)) '#(aval cval))
  (equal-entries? h3 '#((a) (c)) '#(aval cval))
  (equal?
    (begin
      (set! ka (void))
      (collect (collect-maximum-generation))
      (list (hashtable-size h2) (hashtable-size h3)))
    '(1 1))
  (equal-entries? h2 '#((c)) '#(cval))
  (equal-entries? h3 '#((c)) '#(cval))
  (eqv?
    (begin
      (set! h3 (void))
      (collect (collect-maximum-generation))
      (hashtable-size h2))
    1)
  (equal-entries? h2 '#((c)) '#(cval))

 ; test for proper shrinkage
  (eqv?
    (let ([ht (make-eq-hashtable 32)])
      (for-each
        (lambda (k*) (for-each (lambda (k) (eq-hashtable-delete! ht k)) k*))
        (let ([k** (map (lambda (x) (map list (make-list 1000)))
                        (make-list 100))])
          (for-each
            (lambda (k*) (map (lambda (k) (eq-hashtable-set! ht k 75)) k*))
            k**)
          k**))
      (#%$hashtable-veclen ht))
    32)

 ; test for proper shrinkage as objects are bwp'd
 ; uses delete to trigger final shrinkage
  (equal?
    (let* ([ht (make-ephemeron-eq-hashtable 32)]
           [len (#%$hashtable-veclen ht)])
      (eq-hashtable-set! ht 'a 'b)
      (for-each
        (lambda (k*) (map (lambda (k) (eq-hashtable-set! ht k 75)) k*))
        (map (lambda (x) (map list (make-list 1000))) (make-list 100)))
      (collect (collect-maximum-generation))
      (eq-hashtable-delete! ht 'a)
      (list (hashtable-size ht) (= (#%$hashtable-veclen ht) len)))
    '(0 #t))
)

(mat eq-strange
  (begin
    (define $ht (make-eq-hashtable))
    (define $wht (make-weak-eq-hashtable))
    (define $eht (make-ephemeron-eq-hashtable))
    (and (hashtable? $ht)
         (eq-hashtable? $ht)
         (hashtable? $wht)
         (eq-hashtable? $wht)
         (hashtable? $eht)
         (eq-hashtable? $eht)))
  (eqv? (hashtable-set! $ht #f 75) (void))
  (eqv? (hashtable-ref $ht #f 80) 75)
  (eqv? (hashtable-set! $wht #f 75) (void))
  (eqv? (hashtable-ref $wht #f 80) 75)
  (eqv? (hashtable-set! $eht #f 75) (void))
  (eqv? (hashtable-ref $eht #f 80) 75)
  (eqv? (hashtable-set! $ht #!bwp "hello") (void))
  (equal? (hashtable-ref $ht #!bwp "goodbye") "hello")
  (eqv? (hashtable-set! $wht #!bwp "hello") (void))
  (and (member (hashtable-ref $wht #!bwp "goodbye") '("hello" "goodbye")) #t)
  (eqv? (hashtable-set! $eht #!bwp "hello") (void))
  (and (member (hashtable-ref $eht #!bwp "goodbye") '("hello" "goodbye")) #t)
 ; make sure that association isn't added before procedure is called
  (equal?
    (begin
      (hashtable-update! $ht 'cupie
        (lambda (x) (hashtable-ref $ht 'cupie (cons 'barbie x)))
        'doll)
      (hashtable-ref $ht 'cupie 'oops))
   '(barbie . doll))
  (equal?
    (begin
      (hashtable-update! $wht 'cupie
        (lambda (x) (hashtable-ref $wht 'cupie (cons 'barbie x)))
        'doll)
      (hashtable-ref $wht 'cupie 'oops))
   '(barbie . doll))
  (equal?
    (begin
      (hashtable-update! $eht 'cupie
        (lambda (x) (hashtable-ref $eht 'cupie (cons 'barbie x)))
        'doll)
      (hashtable-ref $eht 'cupie 'oops))
   '(barbie . doll))
)

(mat eq-hashtable-stress
 ; stress tests
  (let () ; nonweak
    (define pick
      (lambda (ls)
        (list-ref ls (random (length ls)))))
    (define ht (make-eq-hashtable 4))
    (let ([ls (remq '|| (oblist))] [n 50000])
      (let f ([i 0] [keep '()] [drop '()])
        (if (= i n)
            (and (= (hashtable-size ht) (- n (length drop)))
                 (andmap (lambda (k)
                           (string=?
                             (symbol->string (hashtable-ref ht k #f))
                             (cond
                               [(string? k) k]
                               [(pair? k) (car k)]
                               [(vector? k) (vector-ref k 0)])))
                         keep)
                 (andmap (lambda (k) (eq? (hashtable-ref ht k 'no) 'no))
                         drop))
            (let* ([x (pick ls)] [s (string-copy (symbol->string x))])
              (let ([k (case (pick '(string pair vector))
                         [(string) s]
                         [(pair) (list s)]
                         [(vector) (vector s)])])
                (hashtable-set! ht k x)
                (let ([keep (if (= (modulo i 5) 0) (cons k keep) keep)])
                  (if (= (modulo i 17) 5)
                      (let ([k (pick keep)])
                        (hashtable-delete! ht k)
                        (let ([drop (cons k drop)])
                          (when (= (random 5) 3)
                            (hashtable-delete! ht (pick drop)))
                          (f (+ i 1) (remq k keep) drop)))
                      (f (+ i 1) keep drop)))))))))

  (let () ; weak
    (define pick
      (lambda (ls)
        (list-ref ls (random (length ls)))))
    (define ht (make-weak-eq-hashtable 4))
    (let ([ls (remq '|| (oblist))] [n 50000])
      (let f ([i 0] [keep '()] [drop '()])
        (if (= i n)
            (and (<= (hashtable-size ht) (- n (length drop)))
                 (begin
                   (collect (collect-maximum-generation))
                   (= (hashtable-size ht) (length keep)))
                 (andmap (lambda (k)
                           (string=?
                             (symbol->string (hashtable-ref ht k #f))
                             (cond
                               [(string? k) k]
                               [(pair? k) (car k)]
                               [(vector? k) (vector-ref k 0)])))
                         keep)
                 (andmap (lambda (k) (eq? (hashtable-ref ht k 'no) 'no))
                         drop))
            (let* ([x (pick ls)] [s (string-copy (symbol->string x))])
              (let ([k (case (pick '(string pair vector))
                         [(string) s]
                         [(pair) (list s)]
                         [(vector) (vector s)])])
                (hashtable-set! ht k x)
                (let ([keep (if (= (modulo i 5) 0) (cons k keep) keep)])
                  (if (= (modulo i 17) 5)
                      (let ([k (pick keep)])
                        (hashtable-delete! ht k)
                        (let ([drop (cons k drop)])
                          (when (= (random 5) 3)
                            (hashtable-delete! ht (pick drop)))
                          (f (+ i 1) (remq k keep) drop)))
                      (f (+ i 1) keep drop)))))))))

  (let () ; ephemeron
    (define pick
      (lambda (ls)
        (list-ref ls (random (length ls)))))
    (define ht (make-ephemeron-eq-hashtable 4))
    (let ([ls (remq '|| (oblist))] [n 50000])
      (let f ([i 0] [keep '()] [drop '()])
        (if (= i n)
            (and (<= (hashtable-size ht) (- n (length drop)))
                 (begin
                   (collect (collect-maximum-generation))
                   (= (hashtable-size ht) (length keep)))
                 (andmap (lambda (k)
                           (string=?
                             (symbol->string (hashtable-ref ht k #f))
                             (cond
                               [(string? k) k]
                               [(pair? k) (car k)]
                               [(vector? k) (vector-ref k 0)])))
                         keep)
                 (andmap (lambda (k) (eq? (hashtable-ref ht k 'no) 'no))
                         drop))
            (let* ([x (pick ls)] [s (string-copy (symbol->string x))])
              (let ([k (case (pick '(string pair vector))
                         [(string) s]
                         [(pair) (list s)]
                         [(vector) (vector s)])])
                (hashtable-set! ht k x)
                (let ([keep (if (= (modulo i 5) 0) (cons k keep) keep)])
                  (if (= (modulo i 17) 5)
                      (let ([k (pick keep)])
                        (hashtable-delete! ht k)
                        (let ([drop (cons k drop)])
                          (when (= (random 5) 3)
                            (hashtable-delete! ht (pick drop)))
                          (f (+ i 1) (remq k keep) drop)))
                      (f (+ i 1) keep drop)))))))))

)

(mat nonweak-eqv-hashtable
  (begin
    (define h (make-eqv-hashtable 32))
    (and (hashtable? h)
         (not (eq-hashtable? h))
         (hashtable-mutable? h)
         (not (hashtable-weak? h))
         (not (hashtable-ephemeron? h))))
  (eq? (hashtable-hash-function h) #f)
  (eq? (hashtable-equivalence-function h) eqv?)
  (equal? (hashtable-size h) 0)
  (equal-entries? h '#() '#())
  (eqv? (hashtable-set! h 'a 'aval) (void))
  (equal?
    (list
       (hashtable-contains? h 'a)
       (hashtable-contains? h 3.4)
       (hashtable-contains? h 'c))
    '(#t #f #f))
  (eqv? (hashtable-set! h 3.4 'bval) (void))
  (equal?
    (list
       (hashtable-contains? h 'a)
       (hashtable-contains? h 3.4)
       (hashtable-contains? h 'c))
    '(#t #t #f))
  (eqv? (hashtable-set! h 'c 'cval) (void))
  (equal?
    (list
       (hashtable-contains? h 'a)
       (hashtable-contains? h 3.4)
       (hashtable-contains? h 'c))
    '(#t #t #t))
  (equal? (hashtable-size h) 3)
  (equal-entries? h '#(3.4 c a) '#(bval cval aval))
  #;(same-elements? (list->vector (hashtable-map h cons)) '#((a . aval) (3.4 . bval) (c . cval)))
  #;(same-elements?
    (let ([v (make-vector 3)] [i 0])
      (hashtable-for-each h (lambda (key val) (vector-set! v i (cons key val)) (set! i (fx+ i 1))))
      v)
    '#((a . aval) (3.4 . bval) (c . cval)))
  #;(same-elements?
    (let ([v (make-vector 3)] [i 0])
      (hashtable-for-each-cell h (lambda (a) (vector-set! v i a) (set! i (fx+ i 1))))
      v)
    '#((a . aval) (3.4 . bval) (c . cval)))
  (equal? (hashtable-ref h 'a 1) 'aval)
  (equal? (hashtable-ref h 3.4 #f) 'bval)
  (equal? (hashtable-ref h 'c 'nope) 'cval)
  (eqv? (hashtable-delete! h 3.4) (void))
  (equal? (hashtable-size h) 2)
  (equal-entries? h '#(a c) '#(aval cval))
  (begin
    (define h2 (hashtable-copy h #t))
    (and (hashtable? h2)
         (hashtable-mutable? h2)
         (not (hashtable-weak? h2))
         (not (hashtable-ephemeron? h2))))
  (eq? (hashtable-hash-function h2) #f)
  (eq? (hashtable-equivalence-function h2) eqv?)
  (equal? (hashtable-size h2) 2)
  (equal-entries? h2 '#(a c) '#(aval cval))
  (eqv? (hashtable-clear! h 4) (void))
  (equal?
    (list
      (hashtable-size h)
      (hashtable-ref h 'a 1)
      (hashtable-ref h 3.4 #f)
      (hashtable-ref h 'c 'nope))
   '(0 1 #f nope))
  (equal-entries? h '#() '#())
  (equal?
    (list
      (hashtable-size h2)
      (hashtable-ref h2 'a 1)
      (hashtable-ref h2 3.4 #f)
      (hashtable-ref h2 'c 'nope))
    '(2 aval #f cval))
  (equal-entries? h2 '#(a c) '#(aval cval))
  (eqv?
    (hashtable-update! h 'q
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (hashtable-ref h 'q #f) 18)
  (eqv?
    (hashtable-update! h 'q
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (hashtable-ref h 'q #f) 19)
  (equal? (hashtable-size h) 1)
 ; test hashtable-copy when some keys may have moved
  (let ([t (parameterize ([collect-request-handler void])
             (let ([h4a (make-eqv-hashtable 32)]
                   [k* (map list (make-list 100))])
               (for-each (lambda (x) (hashtable-set! h4a x x)) k*)
               (collect)
              ; create copy after collection but before otherwise touching h4a
               (let ([h4b (hashtable-copy h4a #t)])
                 (andmap
                   (lambda (k) (eqv? (hashtable-ref h4b k #f) k))
                   k*))))])
    (collect)
    t)

 ; test for proper shrinkage
  (equal?
    (let ([ht (make-eqv-hashtable 32)])
      (for-each
        (lambda (k*) (for-each (lambda (k) (hashtable-delete! ht k)) k*))
        (let ([k** (map (lambda (x) (map list (make-list 1000)))
                        (make-list 100))])
          (for-each
            (lambda (k*) (map (lambda (k) (hashtable-set! ht k 75)) k*))
            k**)
          k**))
      (call-with-values (lambda () (#%$hashtable-veclen ht)) cons))
    '(32 . 32))
)

(mat weak-eqv-hashtable
  (begin
    (define ka (list 'a))
    (define kb (list 'b))
    (define kc (list 'c))
    (define kq (list 'q))
    (define ky (list 'y))
    (define kz (list 'z))
    (define km -5.75)
    (define kn 17)
    (define ko (+ (most-positive-fixnum) 5))
    #t)
  (begin
    (define h (make-weak-eqv-hashtable 32))
    (and (hashtable? h)
         (not (eq-hashtable? h))
         (hashtable-mutable? h)
         (hashtable-weak? h)))
  (eq? (hashtable-hash-function h) #f)
  (eq? (hashtable-equivalence-function h) eqv?)
  (equal? (hashtable-size h) 0)
  (equal-entries? h '#() '#())
  (eqv? (hashtable-set! h ka 'aval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc)
       (hashtable-contains? h km)
       (hashtable-contains? h kn)
       (hashtable-contains? h ko))
    '(#t #f #f #f #f #f))
  (eqv? (hashtable-set! h kb 'bval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc)
       (hashtable-contains? h km)
       (hashtable-contains? h kn)
       (hashtable-contains? h ko))
    '(#t #t #f #f #f #f))
  (eqv? (hashtable-set! h kc 'cval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc)
       (hashtable-contains? h km)
       (hashtable-contains? h kn)
       (hashtable-contains? h ko))
    '(#t #t #t #f #f #f))
  (eqv? (hashtable-set! h km 'mval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc)
       (hashtable-contains? h km)
       (hashtable-contains? h kn)
       (hashtable-contains? h ko))
    '(#t #t #t #t #f #f))
  (eqv? (hashtable-set! h kn 'nval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc)
       (hashtable-contains? h km)
       (hashtable-contains? h kn)
       (hashtable-contains? h ko))
    '(#t #t #t #t #t #f))
  (eqv? (hashtable-set! h ko 'oval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc)
       (hashtable-contains? h km)
       (hashtable-contains? h kn)
       (hashtable-contains? h ko))
    '(#t #t #t #t #t #t))
  (equal? (hashtable-size h) 6)
  (equal-entries? h `#((a) (b) (c) -5.75 17 ,ko) '#(aval bval cval mval nval oval))
  #;(same-elements?
    (list->vector (hashtable-map h cons))
    `#(((a) . aval) ((b) . bval) ((c) . cval) (-5.75 . mval) (17 . nval) (,ko . oval)))
  #;(same-elements?
    (let ([v (make-vector 6)] [i 0])
      (hashtable-for-each h (lambda (key val) (vector-set! v i (cons key val)) (set! i (fx+ i 1))))
      v)
    `#(((a) . aval) ((b) . bval) ((c) . cval) (-5.75 . mval) (17 . nval) (,ko . oval)))
  #;(same-elements?
    (let ([v (make-vector 6)] [i 0])
      (hashtable-for-each-cell h (lambda (a) (vector-set! v i a) (set! i (fx+ i 1))))
      v)
    `#(((a) . aval) ((b) . bval) ((c) . cval) (-5.75 . mval) (17 . nval) (,ko . oval)))
  (eq? (hashtable-ref h ka 1) 'aval)
  (eq? (hashtable-ref h kb #f) 'bval)
  (eq? (hashtable-ref h kc 'nope) 'cval)
  (eq? (hashtable-ref h (+ 2 -7.75) 'ugh) 'mval)
  (eq? (hashtable-ref h (/ 34 2) 'ugh) 'nval)
  (eq? (hashtable-ref h (+ (most-positive-fixnum) 7 -2) 'ugh) 'oval)
  (eqv? (hashtable-delete! h kb) (void))
  (equal? (hashtable-size h) 5)
  (equal-entries? h `#((a) (c) -5.75 17 ,ko) '#(aval cval mval nval oval))
  (begin
    (define h2 (hashtable-copy h #t))
    (and (hashtable? h2)
         (hashtable-mutable? h2)
         (hashtable-weak? h2)))
  (eq? (hashtable-hash-function h2) #f)
  (eq? (hashtable-equivalence-function h2) eqv?)
  (equal? (hashtable-size h2) 5)
  (equal-entries? h `#((a) (c) -5.75 17 ,ko) '#(aval cval mval nval oval))
  (eqv? (hashtable-clear! h 4) (void))
  (equal?
    (list
      (hashtable-size h)
      (hashtable-ref h ka 1)
      (hashtable-ref h kb #f)
      (hashtable-ref h kc 'nope)
      (hashtable-ref h km 'nope)
      (hashtable-ref h kn 'nope)
      (hashtable-ref h ko 'nope))
   '(0 1 #f nope nope nope nope))
  (equal-entries? h '#() '#())
  (equal?
    (list
      (hashtable-size h2)
      (hashtable-ref h2 ka 1)
      (hashtable-ref h2 kb #f)
      (hashtable-ref h2 kc 'nope)
      (hashtable-ref h2 (- (+ km 1) 1) 'nope)
      (hashtable-ref h2 (- (+ kn 1) 1) 'nope)
      (hashtable-ref h2 (- (+ ko 1) 1) 'nope))
    '(5 aval #f cval mval nval oval))
  (equal-entries? h2 `#((a) (c) -5.75 17 ,ko) '#(aval cval mval nval oval))
  (eqv?
    (hashtable-update! h kq
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (hashtable-ref h kq #f) 18)
  (eqv?
    (hashtable-update! h kq
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (hashtable-ref h kq #f) 19)
  (equal? (hashtable-size h) 1)
  (equal-entries? h '#((q)) '#(19))
  (eqv?
    (begin
      (set! kq (void))
      (collect (collect-maximum-generation))
      (hashtable-size h))
    0)
  (equal-entries? h '#() '#())
  (equal? (hashtable-ref h ky #f) #f)
  (eqv?
    (hashtable-set! h ky 'toad)
    (void))
  (equal? (hashtable-ref h ky #f) 'toad)
  (equal? (hashtable-ref h kz #f) #f)
  (eqv?
    (hashtable-update! h kz list 'frog)
    (void))
  (equal? (hashtable-ref h kz #f) '(frog))
  (equal-entries?
    h
    (vector kz ky)
    (vector (hashtable-ref h kz #f) 'toad))
  (eqv? (hashtable-ref h '(zippo) 'nil) 'nil)
  (begin
    (define h3 (hashtable-copy h2 #f))
    (and (hashtable? h3)
         (not (hashtable-mutable? h3))
         (hashtable-weak? h3)))
  (equal-entries? h2 `#((a) (c) -5.75 17 ,ko) '#(aval cval mval nval oval))
  (equal-entries? h3 `#((a) (c) -5.75 17 ,ko) '#(aval cval mval nval oval))
  (equal?
    (begin
      (set! ka (void))
      (set! km (void))
      (set! kn (void))
      (set! ko (void))
      (collect (collect-maximum-generation))
      (list (hashtable-size h2) (hashtable-size h3)))
    '(4 4))
  (equal-entries? h2 `#((c) -5.75 17 ,(+ (most-positive-fixnum) 5)) '#(cval mval nval oval))
  (equal-entries? h3 `#((c) -5.75 17 ,(+ (most-positive-fixnum) 5)) '#(cval mval nval oval))
  (eqv?
    (begin
      (set! h3 (void))
      (collect (collect-maximum-generation))
      (hashtable-size h2))
    4)
  (equal-entries? h2 `#((c) -5.75 17 ,(+ (most-positive-fixnum) 5)) '#(cval mval nval oval))

 ; test for proper shrinkage
  (equal?
    (let ([ht (make-eqv-hashtable 32)])
      (for-each
        (lambda (k*) (for-each (lambda (k) (hashtable-delete! ht k)) k*))
        (let ([k** (map (lambda (x) (map list (make-list 1000)))
                        (make-list 100))])
          (for-each
            (lambda (k*) (map (lambda (k) (hashtable-set! ht k 75)) k*))
            k**)
          k**))
      (call-with-values (lambda () (#%$hashtable-veclen ht)) cons))
    '(32 . 32))

 ; test for proper shrinkage as objects are bwp'd
 ; uses delete to trigger final shrinkage
  (equal?
    (let ([ht (make-weak-eqv-hashtable 32)])
      (hashtable-set! ht 'a 'b)
      (for-each
        (lambda (k*) (map (lambda (k) (hashtable-set! ht k 75)) k*))
        (map (lambda (x) (map list (make-list 1000))) (make-list 100)))
      (collect (collect-maximum-generation))
      (hashtable-delete! ht 'a)
      (list (hashtable-size ht)
            (let-values ([(n1 n2) (#%$hashtable-veclen ht)])
              (= n1 n2 32))))
    '(0 #t))
  )

(mat ephemeron-eqv-hashtable
  (begin
    (define ka (list 'a))
    (define kb (list 'b))
    (define kc (list 'c))
    (define kq (list 'q))
    (define ky (list 'y))
    (define kz (list 'z))
    (define km -5.75)
    (define kn 17)
    (define ko (+ (most-positive-fixnum) 5))
    #t)
  (begin
    (define h (make-ephemeron-eqv-hashtable 32))
    (and (hashtable? h)
         (not (eq-hashtable? h))
         (hashtable-mutable? h)
         (hashtable-ephemeron? h)))
  (eq? (hashtable-hash-function h) #f)
  (eq? (hashtable-equivalence-function h) eqv?)
  (equal? (hashtable-size h) 0)
  (equal-entries? h '#() '#())
  (eqv? (hashtable-set! h ka 'aval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc)
       (hashtable-contains? h km)
       (hashtable-contains? h kn)
       (hashtable-contains? h ko))
    '(#t #f #f #f #f #f))
  (eqv? (hashtable-set! h kb 'bval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc)
       (hashtable-contains? h km)
       (hashtable-contains? h kn)
       (hashtable-contains? h ko))
    '(#t #t #f #f #f #f))
  (eqv? (hashtable-set! h kc 'cval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc)
       (hashtable-contains? h km)
       (hashtable-contains? h kn)
       (hashtable-contains? h ko))
    '(#t #t #t #f #f #f))
  (eqv? (hashtable-set! h km 'mval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc)
       (hashtable-contains? h km)
       (hashtable-contains? h kn)
       (hashtable-contains? h ko))
    '(#t #t #t #t #f #f))
  (eqv? (hashtable-set! h kn 'nval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc)
       (hashtable-contains? h km)
       (hashtable-contains? h kn)
       (hashtable-contains? h ko))
    '(#t #t #t #t #t #f))
  (eqv? (hashtable-set! h ko 'oval) (void))
  (equal?
    (list
       (hashtable-contains? h ka)
       (hashtable-contains? h kb)
       (hashtable-contains? h kc)
       (hashtable-contains? h km)
       (hashtable-contains? h kn)
       (hashtable-contains? h ko))
    '(#t #t #t #t #t #t))
  (equal? (hashtable-size h) 6)
  (equal-entries? h `#((a) (b) (c) -5.75 17 ,ko) '#(aval bval cval mval nval oval))
  #;(same-elements?
    (list->vector (hashtable-map h cons))
    `#(((a) . aval) ((b) . bval) ((c) . cval) (-5.75 . mval) (17 . nval) (,ko . oval)))
  #;(same-elements?
    (let ([v (make-vector 6)] [i 0])
      (hashtable-for-each h (lambda (key val) (vector-set! v i (cons key val)) (set! i (fx+ i 1))))
      v)
    `#(((a) . aval) ((b) . bval) ((c) . cval) (-5.75 . mval) (17 . nval) (,ko . oval)))
  #;(same-elements?
    (let ([v (make-vector 6)] [i 0])
      (hashtable-for-each-cell h (lambda (a) (vector-set! v i a) (set! i (fx+ i 1))))
      v)
    `#(((a) . aval) ((b) . bval) ((c) . cval) (-5.75 . mval) (17 . nval) (,ko . oval)))
  (eq? (hashtable-ref h ka 1) 'aval)
  (eq? (hashtable-ref h kb #f) 'bval)
  (eq? (hashtable-ref h kc 'nope) 'cval)
  (eq? (hashtable-ref h (+ 2 -7.75) 'ugh) 'mval)
  (eq? (hashtable-ref h (/ 34 2) 'ugh) 'nval)
  (eq? (hashtable-ref h (+ (most-positive-fixnum) 7 -2) 'ugh) 'oval)
  (eqv? (hashtable-delete! h kb) (void))
  (equal? (hashtable-size h) 5)
  (equal-entries? h `#((a) (c) -5.75 17 ,ko) '#(aval cval mval nval oval))
  (begin
    (define h2 (hashtable-copy h #t))
    (and (hashtable? h2)
         (hashtable-mutable? h2)
         (hashtable-ephemeron? h2)))
  (eq? (hashtable-hash-function h2) #f)
  (eq? (hashtable-equivalence-function h2) eqv?)
  (equal? (hashtable-size h2) 5)
  (equal-entries? h `#((a) (c) -5.75 17 ,ko) '#(aval cval mval nval oval))
  (eqv? (hashtable-clear! h 4) (void))
  (equal?
    (list
      (hashtable-size h)
      (hashtable-ref h ka 1)
      (hashtable-ref h kb #f)
      (hashtable-ref h kc 'nope)
      (hashtable-ref h km 'nope)
      (hashtable-ref h kn 'nope)
      (hashtable-ref h ko 'nope))
   '(0 1 #f nope nope nope nope))
  (equal-entries? h '#() '#())
  (equal?
    (list
      (hashtable-size h2)
      (hashtable-ref h2 ka 1)
      (hashtable-ref h2 kb #f)
      (hashtable-ref h2 kc 'nope)
      (hashtable-ref h2 (- (+ km 1) 1) 'nope)
      (hashtable-ref h2 (- (+ kn 1) 1) 'nope)
      (hashtable-ref h2 (- (+ ko 1) 1) 'nope))
    '(5 aval #f cval mval nval oval))
  (equal-entries? h2 `#((a) (c) -5.75 17 ,ko) '#(aval cval mval nval oval))
  (eqv?
    (hashtable-update! h kq
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (hashtable-ref h kq #f) 18)
  (eqv?
    (hashtable-update! h kq
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (hashtable-ref h kq #f) 19)
  (equal? (hashtable-size h) 1)
  (equal-entries? h '#((q)) '#(19))
  (eqv?
    (begin
      (set! kq (void))
      (collect (collect-maximum-generation))
      (hashtable-size h))
    0)
  (equal-entries? h '#() '#())
  (equal? (hashtable-ref h ky #f) #f)
  (eqv?
    (hashtable-set! h ky 'toad)
    (void))
  (equal? (hashtable-ref h ky #f) 'toad)
  (equal? (hashtable-ref h kz #f) #f)
  (eqv?
    (hashtable-update! h kz list 'frog)
    (void))
  (equal? (hashtable-ref h kz #f) '(frog))
  (equal-entries?
    h
    (vector kz ky)
    (vector (hashtable-ref h kz #f) 'toad))
  (eqv? (hashtable-ref h '(zippo) 'nil) 'nil)
  (begin
    (define h3 (hashtable-copy h2 #f))
    (and (hashtable? h3)
         (not (hashtable-mutable? h3))
         (hashtable-ephemeron? h3)))
  (equal-entries? h2 `#((a) (c) -5.75 17 ,ko) '#(aval cval mval nval oval))
  (equal-entries? h3 `#((a) (c) -5.75 17 ,ko) '#(aval cval mval nval oval))
  (equal?
    (begin
      (set! ka (void))
      (set! km (void))
      (set! kn (void))
      (set! ko (void))
      (collect (collect-maximum-generation))
      (list (hashtable-size h2) (hashtable-size h3)))
    '(4 4))
  (equal-entries? h2 `#((c) -5.75 17 ,(+ (most-positive-fixnum) 5)) '#(cval mval nval oval))
  (equal-entries? h3 `#((c) -5.75 17 ,(+ (most-positive-fixnum) 5)) '#(cval mval nval oval))
  (eqv?
    (begin
      (set! h3 (void))
      (collect (collect-maximum-generation))
      (hashtable-size h2))
    4)
  (equal-entries? h2 `#((c) -5.75 17 ,(+ (most-positive-fixnum) 5)) '#(cval mval nval oval))

 ; test for proper shrinkage
  (equal?
    (let ([ht (make-eqv-hashtable 32)])
      (for-each
        (lambda (k*) (for-each (lambda (k) (hashtable-delete! ht k)) k*))
        (let ([k** (map (lambda (x) (map list (make-list 1000)))
                        (make-list 100))])
          (for-each
            (lambda (k*) (map (lambda (k) (hashtable-set! ht k 75)) k*))
            k**)
          k**))
      (call-with-values (lambda () (#%$hashtable-veclen ht)) cons))
    '(32 . 32))

 ; test for proper shrinkage as objects are bwp'd
 ; uses delete to trigger final shrinkage
  (equal?
    (let ([ht (make-ephemeron-eqv-hashtable 32)])
      (hashtable-set! ht 'a 'b)
      (for-each
        (lambda (k*) (map (lambda (k) (hashtable-set! ht k 75)) k*))
        (map (lambda (x) (map list (make-list 1000))) (make-list 100)))
      (collect (collect-maximum-generation))
      (hashtable-delete! ht 'a)
      (list (hashtable-size ht)
            (let-values ([(n1 n2) (#%$hashtable-veclen ht)])
              (= n1 n2 32))))
    '(0 #t))
)

(mat eqv-hashtable-cell
  (let ()
    (define-record fribble (x))
    (define random-object
      (lambda (x)
        (case (random 9)
          [(0) (cons 'a 3.4)]
          [(1) (vector 'c)]
          [(2) (string #\a #\b)]
          [(3) (make-fribble 'q)]
          [(4) (gensym)]
          [(5) (open-output-string)]
          [(6) (fxvector 15 55)]
          [(7) (lambda () x)]
          [else (box 'top)])))
    (let ([ls1 (let f ([n 10000])
                 (if (fx= n 0)
                     '()
                     (cons
                       (cons (random-object 4) (random-object 7))
                       (f (fx- n 1)))))]
          [ht (make-eqv-hashtable)]
          [wht (make-weak-eqv-hashtable)]
          [eht (make-ephemeron-eqv-hashtable)])
      (let ([ls2 (map (lambda (a1) (hashtable-cell ht (car a1) (cdr a1))) ls1)]
            [ls3 (map (lambda (a1) (hashtable-cell wht (car a1) (cdr a1))) ls1)]
            [ls4 (map (lambda (a1) (hashtable-cell eht (car a1) (cdr a1))) ls1)])
        (unless (andmap (lambda (a1 a2 a3 a4)
                          (and (eqv? (car a1) (car a2))
                               (eqv? (car a2) (car a3))
                               (eqv? (car a2) (car a4))))
                        ls1 ls2 ls3 ls4)
          (errorf #f "keys are not eqv"))
        (unless (andmap (lambda (a1 a2 a3 a4)
                          (and (eqv? (cdr a1) (cdr a2))
                               (eqv? (cdr a2) (cdr a3))
                               (eqv? (cdr a2) (cdr a4))))
                        ls1 ls2 ls3 ls4)
          (errorf #f "values are not eqv"))
        (for-each (lambda (a1)
                    (let ([o (random-object 3)])
                      ;; Value refers to key:
                      (hashtable-set! eht o (list o (car a1)))))
                  ls1)
        (for-each
          (lambda (a1)
            (when (fx< (random 10) 5)
              (set-car! a1 #f)))
          ls1)
        (let loop ([i (min (expt (collect-generation-radix) (collect-maximum-generation)) 1000)])
          (unless (fx= i 0)
            (collect)
            (unless (andmap (lambda (a2 a3 a4) (and (eqv? (car a2) (car a3)) (eqv? (car a2) (car a4))))
                            ls2 ls3 ls4)
              (errorf #f "a2/a3/a4 keys not eqv after collection"))
            (unless (and (andmap (lambda (a3) (not (bwp-object? (car a3)))) ls3)
                         (andmap (lambda (a4) (not (bwp-object? (car a4)))) ls4))
              (errorf #f "keys have been bwp'd"))
            (loop (fx- i 1))))
        (for-each
          (lambda (a2)
            (hashtable-delete! ht (car a2))
            (set-car! a2 #f))
          ls2)
        (unless (and (equal? (hashtable-keys ht) '#())
                     (equal? (hashtable-values ht) '#())
                     (zero? (hashtable-size ht)))
          (errorf #f "ht has not been cleared out"))
        (let loop ([i (min (expt (collect-generation-radix) (collect-maximum-generation)) 1000)])
          (unless (fx= i 0)
            (collect)
            (unless (andmap (lambda (a1 a3 a4)
                              (or (not (car a1))
                                  (and (eqv? (car a1) (car a3))
                                       (eqv? (car a1) (car a4)))))
                            ls1 ls3 ls4)
              (errorf #f "a1/a3/a4 keys not eqv after collection"))
            (loop (fx- i 1))))
        (for-each
          (lambda (a1 a3 a4)
            (unless (or (car a1)
                        (and (bwp-object? (car a3))
                             (bwp-object? (car a4))))
              (errorf #f "~s has not been bwp'd I" (car a3))))
          ls1 ls3 ls4)
        (for-each (lambda (a1) (set-car! a1 #f)) ls1)
        (collect (collect-maximum-generation))
        (unless (and (andmap (lambda (a3) (bwp-object? (car a3))) ls3)
                     (andmap (lambda (a4) (bwp-object? (car a4))) ls4))
          (errorf #f "keys have not been bwp'd II"))
        (unless (and (equal? (hashtable-keys wht) '#())
                     (equal? (hashtable-values wht) '#())
                     (zero? (hashtable-size wht)))
          (errorf #f "wht has not been cleared out"))
        (unless (and (equal? (hashtable-keys eht) '#())
                     (equal? (hashtable-values eht) '#())
                     (zero? (hashtable-size eht)))
          (errorf #f "eht has not been cleared out"))))
    #t)
  )

(mat eqv-strange
  (begin
    (define $ht (make-eqv-hashtable))
    (define $wht (make-weak-eqv-hashtable))
    (define $eht (make-weak-eqv-hashtable))
    (and (hashtable? $ht)
         (hashtable? $wht)
         (hashtable? $eht)))
  (eqv? (hashtable-set! $ht #f 75) (void))
  (eqv? (hashtable-ref $ht #f 80) 75)
  (eqv? (hashtable-set! $wht #f 75) (void))
  (eqv? (hashtable-ref $wht #f 80) 75)
  (eqv? (hashtable-set! $eht #f 75) (void))
  (eqv? (hashtable-ref $eht #f 80) 75)
  (eqv? (hashtable-set! $ht #!bwp "hello") (void))
  (equal? (hashtable-ref $ht #!bwp "goodbye") "hello")
  (eqv? (hashtable-set! $wht #!bwp "hello") (void))
  (eqv? (hashtable-set! $eht #!bwp "hello") (void))
  (and (member (hashtable-ref $wht #!bwp "goodbye") '("hello" "goodbye")) #t)
  (and (member (hashtable-ref $eht #!bwp "goodbye") '("hello" "goodbye")) #t)
 ; make sure that association isn't added before procedure is called
  (equal?
    (begin
      (hashtable-update! $ht 'cupie
        (lambda (x) (hashtable-ref $ht 'cupie (cons 'barbie x)))
        'doll)
      (hashtable-ref $ht 'cupie 'oops))
   '(barbie . doll))
  (equal?
    (begin
      (hashtable-update! $wht 'cupie
        (lambda (x) (hashtable-ref $wht 'cupie (cons 'barbie x)))
        'doll)
      (hashtable-ref $wht 'cupie 'oops))
   '(barbie . doll))
  (equal?
    (begin
      (hashtable-update! $eht 'cupie
        (lambda (x) (hashtable-ref $eht 'cupie (cons 'barbie x)))
        'doll)
      (hashtable-ref $eht 'cupie 'oops))
   '(barbie . doll))
)

(mat eqv-hashtable-stress
 ; stress tests
  (let () ; nonweak
    (define pick
      (lambda (ls)
        (list-ref ls (random (length ls)))))
    (define ht (make-eqv-hashtable 4))
    (let ([ls (remq '|| (oblist))] [n 50000])
      (let f ([i 0] [keep '()] [drop '()])
        (if (= i n)
            (and (= (hashtable-size ht) (- n (length drop)))
                 (andmap (lambda (k)
                           (string=?
                             (symbol->string (hashtable-ref ht k #f))
                             (cond
                               [(string? k) k]
                               [(pair? k) (car k)]
                               [(vector? k) (vector-ref k 0)])))
                         keep)
                 (andmap (lambda (k) (eqv? (hashtable-ref ht k 'no) 'no))
                         drop))
            (let* ([x (pick ls)] [s (string-copy (symbol->string x))])
              (let ([k (case (pick '(string pair vector))
                         [(string) s]
                         [(pair) (list s)]
                         [(vector) (vector s)])])
                (hashtable-set! ht k x)
                (let ([keep (if (= (modulo i 5) 0) (cons k keep) keep)])
                  (if (= (modulo i 17) 5)
                      (let ([k (pick keep)])
                        (hashtable-delete! ht k)
                        (let ([drop (cons k drop)])
                          (when (= (random 5) 3)
                            (hashtable-delete! ht (pick drop)))
                          (f (+ i 1) (remq k keep) drop)))
                      (f (+ i 1) keep drop)))))))))

  (let () ; weak
    (define pick
      (lambda (ls)
        (list-ref ls (random (length ls)))))
    (define ht (make-weak-eqv-hashtable 4))
    (let ([ls (remq '|| (oblist))] [n 50000])
      (let f ([i 0] [keep '()] [drop '()])
        (if (= i n)
            (and (<= (hashtable-size ht) (- n (length drop)))
                 (begin
                   (collect (collect-maximum-generation))
                   (= (hashtable-size ht) (length keep)))
                 (andmap (lambda (k)
                           (string=?
                             (symbol->string (hashtable-ref ht k #f))
                             (cond
                               [(string? k) k]
                               [(pair? k) (car k)]
                               [(vector? k) (vector-ref k 0)])))
                         keep)
                 (andmap (lambda (k) (eqv? (hashtable-ref ht k 'no) 'no))
                         drop))
            (let* ([x (pick ls)] [s (string-copy (symbol->string x))])
              (let ([k (case (pick '(string pair vector))
                         [(string) s]
                         [(pair) (list s)]
                         [(vector) (vector s)])])
                (hashtable-set! ht k x)
                (let ([keep (if (= (modulo i 5) 0) (cons k keep) keep)])
                  (if (= (modulo i 17) 5)
                      (let ([k (pick keep)])
                        (hashtable-delete! ht k)
                        (let ([drop (cons k drop)])
                          (when (= (random 5) 3)
                            (hashtable-delete! ht (pick drop)))
                          (f (+ i 1) (remq k keep) drop)))
                      (f (+ i 1) keep drop)))))))))

  (let () ; ephemeron
    (define pick
      (lambda (ls)
        (list-ref ls (random (length ls)))))
    (define ht (make-ephemeron-eqv-hashtable 4))
    (let ([ls (remq '|| (oblist))] [n 50000])
      (let f ([i 0] [keep '()] [drop '()])
        (if (= i n)
            (and (<= (hashtable-size ht) (- n (length drop)))
                 (begin
                   (collect (collect-maximum-generation))
                   (= (hashtable-size ht) (length keep)))
                 (andmap (lambda (k)
                           (string=?
                             (symbol->string (hashtable-ref ht k #f))
                             (cond
                               [(string? k) k]
                               [(pair? k) (car k)]
                               [(vector? k) (vector-ref k 0)])))
                         keep)
                 (andmap (lambda (k) (eqv? (hashtable-ref ht k 'no) 'no))
                         drop))
            (let* ([x (pick ls)] [s (string-copy (symbol->string x))])
              (let ([k (case (pick '(string pair vector))
                         [(string) s]
                         [(pair) (list s)]
                         [(vector) (vector s)])])
                (hashtable-set! ht k x)
                (let ([keep (if (= (modulo i 5) 0) (cons k keep) keep)])
                  (if (= (modulo i 17) 5)
                      (let ([k (pick keep)])
                        (hashtable-delete! ht k)
                        (let ([drop (cons k drop)])
                          (when (= (random 5) 3)
                            (hashtable-delete! ht (pick drop)))
                          (f (+ i 1) (remq k keep) drop)))
                      (f (+ i 1) keep drop)))))))))

)

(mat symbol-hashtable
  (let ([ht (make-hashtable symbol-hash eq?)])
    (and (symbol-hashtable? ht) (eq? (hashtable-equivalence-function ht) eq?)))
  (let ([ht (make-hashtable symbol-hash eqv?)])
    (and (symbol-hashtable? ht) (eq? (hashtable-equivalence-function ht) eqv?)))
  (let ([ht (make-hashtable symbol-hash equal?)])
    (and (symbol-hashtable? ht) (eq? (hashtable-equivalence-function ht) equal?)))
  (let ([ht (make-hashtable symbol-hash symbol=?)])
    (and (symbol-hashtable? ht) (eq? (hashtable-equivalence-function ht) symbol=?)))
  (let ([ht (make-hashtable symbol-hash eq? 17)])
    (and (symbol-hashtable? ht) (eq? (hashtable-equivalence-function ht) eq?)))
  (let ([ht (make-hashtable symbol-hash eqv? 17)])
    (and (symbol-hashtable? ht) (eq? (hashtable-equivalence-function ht) eqv?)))
  (let ([ht (make-hashtable symbol-hash equal? 17)])
    (and (symbol-hashtable? ht) (eq? (hashtable-equivalence-function ht) equal?)))
  (let ([ht (make-hashtable symbol-hash symbol=? 17)])
    (and (symbol-hashtable? ht) (eq? (hashtable-equivalence-function ht) symbol=?)))
  (begin
    (define h (make-hashtable symbol-hash eq? 32))
    (and (hashtable? h)
         (symbol-hashtable? h)
         (hashtable-mutable? h)
         (not (eq-hashtable? h))
         (not (hashtable-weak? h))
         (not (hashtable-ephemeron? h))))
  (eq? (hashtable-hash-function h) symbol-hash)
  (eq? (hashtable-equivalence-function h) eq?)
  (equal? (hashtable-size h) 0)
  (equal-entries? h '#() '#())
  (eqv? (hashtable-set! h 'a 'aval) (void))
  (equal?
    (list
       (hashtable-contains? h 'a)
       (hashtable-contains? h 'b)
       (hashtable-contains? h 'c))
    '(#t #f #f))
  (eqv? (hashtable-set! h 'b 'bval) (void))
  (equal?
    (list
       (hashtable-contains? h 'a)
       (hashtable-contains? h 'b)
       (hashtable-contains? h 'c))
    '(#t #t #f))
  (eqv? (hashtable-set! h 'c 'cval) (void))
  (equal?
    (list
       (hashtable-contains? h 'a)
       (hashtable-contains? h 'b)
       (hashtable-contains? h 'c))
    '(#t #t #t))
  (equal? (hashtable-size h) 3)
  (equal-entries? h '#(b c a) '#(bval cval aval))
  #;(same-elements? (list->vector (hashtable-map h cons)) '#((a . aval) (b . bval) (c . cval)))
  #;(same-elements?
    (let ([v (make-vector 3)] [i 0])
      (hashtable-for-each h (lambda (key val) (vector-set! v i (cons key val)) (set! i (fx+ i 1))))
      v)
    '#((a . aval) (b . bval) (c . cval)))
  #;(same-elements?
    (let ([v (make-vector 3)] [i 0])
      (hashtable-for-each-cell h (lambda (a) (vector-set! v i a) (set! i (fx+ i 1))))
      v)
    '#((a . aval) (b . bval) (c . cval)))
  (equal? (hashtable-ref h 'a 1) 'aval)
  (equal? (hashtable-ref h 'b #f) 'bval)
  (equal? (hashtable-ref h 'c 'nope) 'cval)
  (eqv? (hashtable-delete! h 'b) (void))
  (equal? (hashtable-size h) 2)
  (equal-entries? h '#(a c) '#(aval cval))
  (begin
    (define h2 (hashtable-copy h #t))
    (and (hashtable? h2)
         (symbol-hashtable? h2)
         (hashtable-mutable? h2)
         (not (hashtable-weak? h2))
         (not (hashtable-ephemeron? h2))
         (not (eq-hashtable? h2))))
  (eq? (hashtable-hash-function h2) symbol-hash)
  (eq? (hashtable-equivalence-function h2) eq?)
  (equal? (hashtable-size h2) 2)
  (equal-entries? h2 '#(a c) '#(aval cval))
  (eqv? (hashtable-clear! h 4) (void))
  (equal?
    (list
      (hashtable-size h)
      (hashtable-ref h 'a 1)
      (hashtable-ref h 'b #f)
      (hashtable-ref h 'c 'nope))
   '(0 1 #f nope))
  (equal-entries? h '#() '#())
  (equal?
    (list
      (hashtable-size h2)
      (hashtable-ref h2 'a 1)
      (hashtable-ref h2 'b #f)
      (hashtable-ref h2 'c 'nope))
    '(2 aval #f cval))
  (equal-entries? h2 '#(a c) '#(aval cval))
  (eqv?
    (hashtable-update! h 'q
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (hashtable-ref h 'q #f) 18)
  (eqv?
    (hashtable-update! h 'q
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (hashtable-ref h 'q #f) 19)
  (equal? (hashtable-size h) 1)
 ; test hashtable-copy when some keys may have moved
 ; symbol hashes don't change, but keeping test adapted from eq-hashtable mats anyway
  (let ([t (parameterize ([collect-request-handler void])
             (let ([h4a (make-hashtable symbol-hash eqv? 32)]
                   [k* (list-head (oblist) 100)])
               (for-each (lambda (x) (hashtable-set! h4a x x)) k*)
               (collect)
              ; create copy after collection but before otherwise touching h4a
               (let ([h4b (hashtable-copy h4a #t)])
                 (andmap
                   (lambda (k) (eq? (hashtable-ref h4b k #f) k))
                   k*))))])
    (collect)
    t)
 ; test for proper shrinkage
  (eqv?
    (let ([ht (make-hashtable symbol-hash equal? 32)])
      (for-each
        (lambda (k*) (for-each (lambda (k) (hashtable-delete! ht k)) k*))
        (let ([k** (map (lambda (x) (list-head (oblist) 1000)) (make-list 100))])
          (for-each
            (lambda (k*) (map (lambda (k) (hashtable-set! ht k 75)) k*))
            k**)
          k**))
      (#%$hashtable-veclen ht))
    32)
)

(mat $symbol-hashtable
  (begin
    (define h (make-hashtable symbol-hash eq? 32))
    (and (hashtable? h)
         (symbol-hashtable? h)
         (hashtable-mutable? h)
         (not (eq-hashtable? h))
         (not (hashtable-weak? h))
         (not (hashtable-ephemeron? h))))
  (eq? (hashtable-hash-function h) symbol-hash)
  (eq? (hashtable-equivalence-function h) eq?)
  (equal? (hashtable-size h) 0)
  (equal-entries? h '#() '#())
  (eqv? (symbol-hashtable-set! h 'a 'aval) (void))
  (equal?
    (list
       (symbol-hashtable-contains? h 'a)
       (symbol-hashtable-contains? h 'b)
       (symbol-hashtable-contains? h 'c))
    '(#t #f #f))
  (eqv? (symbol-hashtable-set! h 'b 'bval) (void))
  (equal?
    (list
       (symbol-hashtable-contains? h 'a)
       (symbol-hashtable-contains? h 'b)
       (symbol-hashtable-contains? h 'c))
    '(#t #t #f))
  (eqv? (symbol-hashtable-set! h 'c 'cval) (void))
  (equal?
    (list
       (symbol-hashtable-contains? h 'a)
       (symbol-hashtable-contains? h 'b)
       (symbol-hashtable-contains? h 'c))
    '(#t #t #t))
  (equal? (hashtable-size h) 3)
  (equal-entries? h '#(b c a) '#(bval cval aval))
  #;(same-elements? (list->vector (hashtable-map h cons)) '#((a . aval) (b . bval) (c . cval)))
  #;(same-elements?
    (let ([v (make-vector 3)] [i 0])
      (hashtable-for-each h (lambda (key val) (vector-set! v i (cons key val)) (set! i (fx+ i 1))))
      v)
    '#((a . aval) (b . bval) (c . cval)))
  #;(same-elements?
    (let ([v (make-vector 3)] [i 0])
      (hashtable-for-each-cell h (lambda (a) (vector-set! v i a) (set! i (fx+ i 1))))
      v)
    '#((a . aval) (b . bval) (c . cval)))
  (equal? (symbol-hashtable-ref h 'a 1) 'aval)
  (equal? (symbol-hashtable-ref h 'b #f) 'bval)
  (equal? (symbol-hashtable-ref h 'c 'nope) 'cval)
  (eqv? (symbol-hashtable-delete! h 'b) (void))
  (equal? (hashtable-size h) 2)
  (equal-entries? h '#(a c) '#(aval cval))
  (begin
    (define h2 (hashtable-copy h #t))
    (and (hashtable? h2)
         (symbol-hashtable? h2)
         (hashtable-mutable? h2)
         (not (hashtable-weak? h2))
         (not (hashtable-ephemeron? h2))
         (not (eq-hashtable? h2))))
  (eq? (hashtable-hash-function h2) symbol-hash)
  (eq? (hashtable-equivalence-function h2) eq?)
  (equal? (hashtable-size h2) 2)
  (equal-entries? h2 '#(a c) '#(aval cval))
  (eqv? (hashtable-clear! h 4) (void))
  (equal?
    (list
      (hashtable-size h)
      (symbol-hashtable-ref h 'a 1)
      (symbol-hashtable-ref h 'b #f)
      (symbol-hashtable-ref h 'c 'nope))
   '(0 1 #f nope))
  (equal-entries? h '#() '#())
  (equal?
    (list
      (hashtable-size h2)
      (symbol-hashtable-ref h2 'a 1)
      (symbol-hashtable-ref h2 'b #f)
      (symbol-hashtable-ref h2 'c 'nope))
    '(2 aval #f cval))
  (equal-entries? h2 '#(a c) '#(aval cval))
  (eqv?
    (symbol-hashtable-update! h 'q
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (symbol-hashtable-ref h 'q #f) 18)
  (eqv?
    (symbol-hashtable-update! h 'q
      (lambda (x) (+ x 1))
      17)
    (void))
  (equal? (symbol-hashtable-ref h 'q #f) 19)
  (equal? (hashtable-size h) 1)
  (let ([g (gensym)] [s "feisty"])
    (let ([a (symbol-hashtable-cell h g s)])
      (and (pair? a)
           (eq? (car a) g)
           (eq? (cdr a) s)
           (begin
             (hashtable-set! h g 'feisty)
             (eq? (cdr a) 'feisty))
           (begin
             (set-cdr! a (list "feisty"))
             (equal? (hashtable-ref h g #f) '("feisty"))))))
 ; test hashtable-copy when some keys may have moved
 ; symbol hashes don't change, but keeping test adapted from eq-hashtable mats anyway
  (let ([t (parameterize ([collect-request-handler void])
             (let ([h4a (make-hashtable symbol-hash eqv? 32)]
                   [k* (list-head (oblist) 100)])
               (for-each (lambda (x) (symbol-hashtable-set! h4a x x)) k*)
               (collect)
              ; create copy after collection but before otherwise touching h4a
               (let ([h4b (hashtable-copy h4a #t)])
                 (andmap
                   (lambda (k) (eq? (symbol-hashtable-ref h4b k #f) k))
                   k*))))])
    (collect)
    t)
 ; test for proper shrinkage
  (eqv?
    (let ([ht (make-hashtable symbol-hash equal? 32)])
      (for-each
        (lambda (k*) (for-each (lambda (k) (symbol-hashtable-delete! ht k)) k*))
        (let ([k** (map (lambda (x) (list-head (oblist) 1000)) (make-list 100))])
          (for-each
            (lambda (k*) (map (lambda (k) (symbol-hashtable-set! ht k 75)) k*))
            k**)
          k**))
      (#%$hashtable-veclen ht))
    32)
)

(mat symbol-hashtable-stress
 ; stress tests
  (let () ; nonweak
    (define pick
      (lambda (ls)
        (list-ref ls (random (length ls)))))
    (define ht (make-hashtable symbol-hash eq? 4))
    (let ([ls (remq '|| (oblist))] [n 50000])
      (let f ([i 0] [keep '()] [drop '()])
        (if (= i n)
            (and (= (hashtable-size ht) (- n (length drop)))
                 (andmap (lambda (k)
                           (string=?
                             (symbol->string (hashtable-ref ht k #f))
                             (symbol->string k)))
                         keep)
                 (andmap (lambda (k) (eq? (hashtable-ref ht k 'no) 'no))
                         drop))
            (let* ([x (pick ls)] [s (string-copy (symbol->string x))])
              (let ([k (gensym s)])
                (hashtable-set! ht k x)
                (let ([keep (if (= (modulo i 5) 0) (cons k keep) keep)])
                  (if (= (modulo i 17) 5)
                      (let ([k (pick keep)])
                        (hashtable-delete! ht k)
                        (let ([drop (cons k drop)])
                          (when (= (random 5) 3)
                            (hashtable-delete! ht (pick drop)))
                          (f (+ i 1) (remq k keep) drop)))
                      (f (+ i 1) keep drop)))))))))
)

(mat generic-hashtable
  (begin
    (define $ght-keys1 '#(a b c d e f g))
    (define $ght-vals1 '#(1 3 5 7 9 11 13))
    (define $ght (make-hashtable equal-hash equal? 8))
    (vector-for-each
      (lambda (x i) (hashtable-set! $ght x i))
      $ght-keys1
      $ght-vals1)
    (hashtable? $ght))
  (not (eq-hashtable? $ght))
  (eq? (hashtable-hash-function $ght) equal-hash)
  (eq? (hashtable-equivalence-function $ght) equal?)
  (eq? (hashtable-mutable? $ght) #t)
  (not (hashtable-weak? $ght))
  (not (hashtable-ephemeron? $ght))
  (eqv? (hashtable-size $ght) (vector-length $ght-keys1))
  (eqv? (#%$hashtable-veclen $ght) 8)
  (equal-entries? $ght $ght-keys1 $ght-vals1)
  (begin
    (define $ght-keys2 '#((a . b) (1 . 2) 3/4 3.4 3.5 1e23 #e1e50 1+1i 3+3.2i -15 #e1e-50 #1=(a . #1#) (#2=(#2# b c))))
    (define $ght-vals2 '#(a b c d e f g h i j k l m))
    (vector-for-each
      (lambda (x i) (hashtable-set! $ght x i))
      $ght-keys2
      $ght-vals2)
    (eq? (hashtable-size $ght) (+ (vector-length $ght-keys1) (vector-length $ght-keys2))))
  (> (#%$hashtable-veclen $ght) 8)
  (equal-entries? $ght ($vector-append $ght-keys1 $ght-keys2) ($vector-append $ght-vals1 $ght-vals2))
  #;(same-elements?
    (list->vector (hashtable-map $ght cons))
    (vector-map cons ($vector-append $ght-keys1 $ght-keys2) ($vector-append $ght-vals1 $ght-vals2)))
  #;(same-elements?
    (let ([v (make-vector (+ (vector-length $ght-keys1) (vector-length $ght-keys2)))] [i 0])
      (hashtable-for-each $ght (lambda (key val) (vector-set! v i (cons key val)) (set! i (fx+ i 1))))
      v)
    (vector-map cons ($vector-append $ght-keys1 $ght-keys2) ($vector-append $ght-vals1 $ght-vals2)))
  #;(same-elements?
    (let ([v (make-vector (+ (vector-length $ght-keys1) (vector-length $ght-keys2)))] [i 0])
      (hashtable-for-each-cell $ght (lambda (a) (vector-set! v i a) (set! i (fx+ i 1))))
      v)
    (vector-map cons ($vector-append $ght-keys1 $ght-keys2) ($vector-append $ght-vals1 $ght-vals2)))
  ($vector-andmap
    (lambda (k v) (equal? (hashtable-ref $ght k #f) v))
    $ght-keys1
    $ght-vals1)
  ($vector-andmap
    (lambda (k v) (equal? (hashtable-ref $ght k #f) v))
    $ght-keys2
    $ght-vals2)
  ($vector-andmap
    (lambda (k v) (equal? (hashtable-ref $ght k #f) v))
    '#((a . b) (1 . 2) 3/4 3.4 3.5 1e23 #e1e50 1+1i 3+3.2i -15 #e1e-50 #3=(a . #3#) (#4=(#4# b c)))
    $ght-vals2)
  ($vector-andmap
    (lambda (k) (hashtable-contains? $ght k))
    $ght-keys1)
  ($vector-andmap
    (lambda (k) (hashtable-contains? $ght k))
    $ght-keys2)
  (not (hashtable-contains? $ght '(not a key)))
  (eq? (hashtable-ref $ght '(not a key) 'not-a-key) 'not-a-key)
  (begin
    (define $ght2 (hashtable-copy $ght))
    (and (hashtable? $ght2)
         (not (hashtable-mutable? $ght2))
         (not (hashtable-weak? $ght2))
         (not (hashtable-ephemeron? $ght2))))
  (eq? (hashtable-hash-function $ght) equal-hash)
  (eq? (hashtable-equivalence-function $ght) equal?)
  (begin
    (define $ght3 (hashtable-copy $ght #t))
    (and (hashtable? $ght3)
         (hashtable-mutable? $ght3)
         (not (hashtable-weak? $ght3))
         (not (hashtable-ephemeron? $ght3))))
  (eq? (hashtable-hash-function $ght) equal-hash)
  (eq? (hashtable-equivalence-function $ght) equal?)
  (begin
    (vector-for-each
      (lambda (k) (hashtable-delete! $ght k))
      $ght-keys1)
    #t)
  (equal-entries? $ght $ght-keys2 $ght-vals2)
  (eqv? (hashtable-size $ght) (vector-length $ght-keys2))
  (begin
    (vector-for-each
      (lambda (k) (hashtable-delete! $ght k))
      $ght-keys2)
    #t)
  (equal-entries? $ght '#() '#())
  (eqv? (hashtable-size $ght) 0)
  (eqv? (#%$hashtable-veclen $ght) 8)
 ; make sure copies are unaffected by deletions
  (eq? (hashtable-size $ght2) (+ (vector-length $ght-keys1) (vector-length $ght-keys2)))
  (equal-entries? $ght2 ($vector-append $ght-keys1 $ght-keys2) ($vector-append $ght-vals1 $ght-vals2))
  (eq? (hashtable-size $ght3) (+ (vector-length $ght-keys1) (vector-length $ght-keys2)))
  (equal-entries? $ght3 ($vector-append $ght-keys1 $ght-keys2) ($vector-append $ght-vals1 $ght-vals2))
  (begin
    (hashtable-clear! $ght3)
    (and
      (eqv? (hashtable-size $ght3) 0)
      (eqv? (hashtable-size $ght2) (+ (vector-length $ght-keys1) (vector-length $ght-keys2)))))
  (error? ; not mutable
    (hashtable-clear! $ght2))
  (error? ; not mutable
    (hashtable-delete! $ght2 (vector-ref $ght-keys2 0)))
  (error? ; not mutable
    (hashtable-update! $ght2 (vector-ref $ght-keys2 0)
      (lambda (x) (cons x x))
      'oops))
  (error? ; not mutable
    (hashtable-update! $ght2 '(not a key)
      (lambda (x) (cons x x))
      'oops))
  (eqv?
    (hashtable-update! $ght3 '(a . b)
      (lambda (x) (+ x 15))
      17)
    (void))
  (eqv?
    (hashtable-update! $ght3 '(a . b)
      (lambda (x) (+ x 29))
      17)
    (void))
  (eqv?
    (hashtable-update! $ght3 1e23
      (lambda (x) (- x 5))
      19)
    (void))
  (equal?
    (let ([a (hashtable-cell $ght3 '(a . b) 17)])
      (set-cdr! a (+ (cdr a) 100))
      a)
    '((a . b) . 161))
  (equal?
    (let ([a (hashtable-cell $ght3 #vu8(1 2 3) 'bv)])
      (set-cdr! a (cons (cdr a) 'vb))
      a)
    '(#vu8(1 2 3) . (bv . vb)))
  (equal-entries? $ght3 '#((a . b) 1e23 #vu8(1 2 3)) '#(161 14 (bv . vb)))
  (let () ; carl's test program, with a few additions
    (define cov:prof-hash
      (lambda (V)
        (* (vector-ref V 0) (vector-ref V 1) (vector-ref V 2))))
    (define cov:prof-equal?
      (lambda (V W)
        (let ((rv (and (= (vector-ref V 0) (vector-ref W 0))
                       (= (vector-ref V 1) (vector-ref W 1))
                       (= (vector-ref V 2) (vector-ref W 2)))))
          rv)))
    (define make-random-vector-key
      (lambda ()
        (vector (random 20000) (random 100) (random 1000))))
    (define test-hash
      (lambda (n)
        (let ([ht (make-hashtable cov:prof-hash cov:prof-equal?)])
          (let loop ([i 0])
            (let ([str (make-random-vector-key)])
              (hashtable-set! ht str i)
              (hashtable-update! ht str (lambda (x) (* x 2)) -1)
              (let ([a (hashtable-cell ht str 'a)]) (set-cdr! a (- (cdr a))))
              (cond
                [(= i n) (= (hashtable-size ht) 1000)]
                [(and (hashtable-contains? ht str)
                      (= (hashtable-ref ht str #f) (* i -2)))
                 (when (= (hashtable-size ht) 1000)
                   (hashtable-delete! ht str))
                 (loop (+ i 1))]
                [else (errorf 'test-hash "hashtable failure for key ~s" str)]))))))
    (test-hash 100000))
)

(mat hash-functions
 ; equal-hash
  (error? ; wrong argument count
    (equal-hash))
  (error? ; wrong argument count
    (equal-hash 0 0))
 ; symbol-hash
  (error? ; wrong argument count
    (symbol-hash))
  (error? ; wrong argument count
    (symbol-hash 'a 'a))
  (error? ; not a symbol
    (symbol-hash "hello"))
 ; string-hash
  (error? ; wrong argument count
    (string-hash))
  (error? ; wrong argument count
    (string-hash 'a 'a))
  (error? ; not a string
    (string-hash 'hello))
 ; string-ci-hash
  (error? ; wrong argument count
    (string-ci-hash))
  (error? ; wrong argument count
    (string-ci-hash 'a 'a))
  (error? ; not a string
    (string-ci-hash 'hello))
  (let ([hc (equal-hash '(a b c))])
    (and (integer? hc)
         (exact? hc)
         (>= hc 0)
         (= (equal-hash '(a b c)) hc)))
  (let ([hc (string-hash "hello")])
    (and (integer? hc)
         (exact? hc)
         (>= hc 0)
         (= (string-hash "hello") hc)))
  (let ([hc (string-ci-hash "hello")])
    (and (integer? hc)
         (exact? hc)
         (>= hc 0)
         (= (string-ci-hash "HelLo") hc)))
  (let f ([ls (oblist)])
    (define okay?
      (lambda (x)
        (let ([hc (symbol-hash x)])
          (and (integer? hc)
               (exact? hc)
               (>= hc 0)
               (= (symbol-hash x) hc)))))
    (and (okay? (car ls))
         (let g ([ls ls] [n 10])
           (or (null? ls)
               (if (= n 0)
                   (f ls)
                   (g (cdr ls) (- n 1)))))))
 ; adapted from Flatt's r6rs tests for string-ci=?
  (eqv? (string-ci-hash "z") (string-ci-hash "Z"))
  (not (eqv? (string-ci-hash "z") (string-ci-hash "a")))
  (eqv? (string-ci-hash "Stra\xDF;e") (string-ci-hash "Strasse"))
  (eqv? (string-ci-hash "Stra\xDF;e") (string-ci-hash "STRASSE"))
  (eqv? (string-ci-hash "\x39E;\x391;\x39F;\x3A3;") (string-ci-hash "\x3BE;\x3B1;\x3BF;\x3C2;"))
  (eqv? (string-ci-hash "\x39E;\x391;\x39F;\x3A3;") (string-ci-hash "\x3BE;\x3B1;\x3BF;\x3C3;"))
)

(mat fasl-eq-hashtable
 ; fasling out eq hash tables
  (equal?
    (let ([x (cons 'y '!)])
      (define ht (make-eq-hashtable))
      (eq-hashtable-set! ht x 'because)
      (eq-hashtable-set! ht 'foo "foo")
      (let ([p (open-file-output-port "testfile.ss" (file-options replace))])
        (fasl-write (list x ht) p)
        (close-port p))
      (let-values ([(x2 ht2)
                    (apply values
                      (call-with-port
                        (open-file-input-port "testfile.ss")
                        fasl-read))])
        (list
          (eq-hashtable-weak? ht2)
          (eq-hashtable-ephemeron? ht2)
          (eq-hashtable-ref ht2 x2 #f)
          (eq-hashtable-ref ht2 'foo #f))))
    '(#f #f because "foo"))
 ; fasling out weak eq hash table
  (equal?
    (with-interrupts-disabled
      (let ([x (cons 'y '!)])
        (define ht (make-weak-eq-hashtable))
        (eq-hashtable-set! ht x 'because)
        (eq-hashtable-set! ht 'foo "foo")
        (let ([p (open-file-output-port "testfile.ss" (file-options replace))])
          (fasl-write (list x ht) p)
          (close-port p))
        (let-values ([(x2 ht2)
                      (apply values
                        (call-with-port
                          (open-file-input-port "testfile.ss")
                          fasl-read))])
          (list
            (eq-hashtable-weak? ht2)
            (eq-hashtable-ephemeron? ht2)
            (eq-hashtable-ref ht2 x2 #f)
            (eq-hashtable-ref ht2 'foo #f)))))
    '(#t #f because "foo"))
  (equal?
    (let ([ht2 (cadr (call-with-port
                       (open-file-input-port "testfile.ss")
                       fasl-read))])
      (collect (collect-maximum-generation))
      (list
        (hashtable-keys ht2)
        (eq-hashtable-ref ht2 'foo #f)))
    '(#(foo) "foo"))
 ; fasling out ephemeron eq hash table
  (equal?
    (with-interrupts-disabled
      (let ([x (cons 'y '!)])
        (define ht (make-ephemeron-eq-hashtable))
        (eq-hashtable-set! ht x 'because)
        (eq-hashtable-set! ht 'foo "foo")
        (let ([p (open-file-output-port "testfile.ss" (file-options replace))])
          (fasl-write (list x ht) p)
          (close-port p))
        (let-values ([(x2 ht2)
                      (apply values
                        (call-with-port
                          (open-file-input-port "testfile.ss")
                          fasl-read))])
          (list
            (eq-hashtable-weak? ht2)
            (eq-hashtable-ephemeron? ht2)
            (eq-hashtable-ref ht2 x2 #f)
            (eq-hashtable-ref ht2 'foo #f)))))
    '(#f #t because "foo"))
  (equal?
    (let ([ht2 (cadr (call-with-port
                       (open-file-input-port "testfile.ss")
                       fasl-read))])
      (collect (collect-maximum-generation))
      (list
        (hashtable-keys ht2)
        (eq-hashtable-ref ht2 'foo #f)))
    '(#(foo) "foo"))
 ; fasling eq hash tables via compile-file
  (begin
    (with-output-to-file "testfile.ss"
      (lambda ()
        (pretty-print
          '(module ($feh-ls $feh-ht)
             (define-syntax ls
               (let ([ls '(1 2 3)])
                 (lambda (x)
                   #`(quote #,(datum->syntax #'* ls)))))
             (define $feh-ls ls)
             (define $feh-ht
               (let ()
                 (define-syntax a
                   (let ([ht (make-eq-hashtable)])
                     (eq-hashtable-set! ht 'q 'p)
                     (eq-hashtable-set! ht ls (cdr ls))
                     (eq-hashtable-set! ht (cdr ls) (cddr ls))
                     (eq-hashtable-set! ht (cddr ls) ls)
                     (lambda (x) #`(quote #,(datum->syntax #'* ht)))))
                 a)))))
      'replace)
    (compile-file "testfile")
    (load "testfile.so")
    #t)
  (eq? (eq-hashtable-ref $feh-ht 'q #f) 'p)
  (eq? (eq-hashtable-ref $feh-ht $feh-ls #f) (cdr $feh-ls))
  (eq? (eq-hashtable-ref $feh-ht (cdr $feh-ls) #f) (cddr $feh-ls))
  (eq? (eq-hashtable-ref $feh-ht (cddr $feh-ls) #f) $feh-ls)
  (begin
    (eq-hashtable-set! $feh-ht 'p 'r)
    #t)
  (eq? (eq-hashtable-ref $feh-ht 'p #f) 'r)
  (begin
    (eq-hashtable-set! $feh-ht 'q 'not-p)
    #t)
  (eq? (eq-hashtable-ref $feh-ht 'q #f) 'not-p)
)

(mat fasl-symbol-hashtable
 ; fasling out symbol hash tables
  (equal?
    (let ()
      (define ht (make-hashtable symbol-hash eq?))
      (symbol-hashtable-set! ht 'why? 'because)
      (symbol-hashtable-set! ht 'foo "foo")
      (let ([p (open-file-output-port "testfile.ss" (file-options replace))])
        (fasl-write ht p)
        (close-port p))
      (let ([ht2 (call-with-port (open-file-input-port "testfile.ss") fasl-read)])
        (list
          (symbol-hashtable-ref ht2 'why? #f)
          (symbol-hashtable-ref ht2 'foo #f))))
    '(because "foo"))
  (#%$fasl-file-equal? "testfile.ss" "testfile.ss")
  (eqv? (strip-fasl-file "testfile.ss" "testfile1.ss" (fasl-strip-options)) (void))
  (#%$fasl-file-equal? "testfile.ss" "testfile1.ss")
  (equal?
    (let ([ht2 (call-with-port (open-file-input-port "testfile1.ss" (file-options compressed)) fasl-read)])
      (list
        (symbol-hashtable-ref ht2 'why? #f)
        (symbol-hashtable-ref ht2 'foo #f)))
    '(because "foo"))
  (begin
    (call-with-port (open-file-output-port "testfile1.ss" (file-options replace))
      (lambda (p)
        (fasl-write (call-with-port (open-file-input-port "testfile.ss") fasl-read) p)))
    #t)
  (#%$fasl-file-equal? "testfile.ss" "testfile1.ss")
  (#%$fasl-file-equal? "testfile1.ss" "testfile.ss")
  (begin
    (call-with-port (open-file-output-port "testfile1.ss" (file-options replace))
      (lambda (p)
        (let ([ht (call-with-port (open-file-input-port "testfile.ss") fasl-read)])
          (symbol-hashtable-set! ht 'why? 'why-not?)
          (fasl-write ht p))))
    #t)
  (not (#%$fasl-file-equal? "testfile.ss" "testfile1.ss"))
  (not (#%$fasl-file-equal? "testfile1.ss" "testfile.ss"))
  (begin
    (call-with-port (open-file-output-port "testfile1.ss" (file-options replace))
      (lambda (p)
        (let ([ht (call-with-port (open-file-input-port "testfile.ss") fasl-read)])
          (symbol-hashtable-set! ht (gensym) 'foiled)
          (fasl-write ht p))))
    #t)
  (not (#%$fasl-file-equal? "testfile.ss" "testfile1.ss"))
  (not (#%$fasl-file-equal? "testfile1.ss" "testfile.ss"))

 ; fasling symbol hash tables via compile-file
  (begin
    (with-output-to-file "testfile.ss"
      (lambda ()
        (pretty-print
          '(define $fsh-ht
             (let ()
               (define-syntax a
                 (let ([ht (make-hashtable symbol-hash symbol=?)])
                   (symbol-hashtable-set! ht 'q 'p)
                   (symbol-hashtable-set! ht 'p 's)
                   (let ([g (gensym "hello")])
                     (symbol-hashtable-set! ht g g)
                     (symbol-hashtable-set! ht 'g g))
                   (lambda (x) #`(quote #,(datum->syntax #'* ht)))))
               a))))
      'replace)
    (compile-file "testfile")
    (load "testfile.so")
    #t)
  (eq? (symbol-hashtable-ref $fsh-ht 'q #f) 'p)
  (eq? (symbol-hashtable-ref $fsh-ht 'p #f) 's)
  (let ([g (symbol-hashtable-ref $fsh-ht 'g #f)])
    (eq? (symbol-hashtable-ref $fsh-ht g #f) g))
  (eq? (symbol-hashtable-ref $fsh-ht 'spam #f) #f)
  (begin
    (symbol-hashtable-set! $fsh-ht 'p 'r)
    #t)
  (eq? (symbol-hashtable-ref $fsh-ht 'p #f) 'r)
  (begin
    (symbol-hashtable-set! $fsh-ht 'q 'not-p)
    #t)
  (eq? (symbol-hashtable-ref $fsh-ht 'q #f) 'not-p)
)

(mat fasl-other-hashtable
 ; can't fasl out other kinds of hashtables
  (error?
    (let ([x (cons 'y '!)])
      (define ht (make-eqv-hashtable))
      (hashtable-set! ht x 'because)
      (hashtable-set! ht 'foo "foo")
      (hashtable-set! ht 3.1415 "pi")
      (let ([p (open-file-output-port "testfile.ss" (file-options replace))])
        (with-exception-handler
          (lambda (c) (close-port p) (raise-continuable c))
          (lambda () (fasl-write (list x ht) p))))))
  (error?
    (let ([x (cons 'y '!)])
      (define ht (make-hashtable string-hash string=?))
      (hashtable-set! ht "hello" 'goodbye)
      (let ([p (open-file-output-port "testfile.ss" (file-options replace))])
        (with-exception-handler
          (lambda (c) (close-port p) (raise-continuable c))
          (lambda () (fasl-write (list x ht) p))))))
)

(mat ht
  (begin
    (display-string (separate-eval '(parameterize ([source-directories '("." "../s" "../../s")]) (load "ht.ss"))))
    #t)
)
